Skip to content

A slot map is a high-performance associative container with persistent unique 32/64 bit keys to access stored values.

License

Notifications You must be signed in to change notification settings

SergeyMakeev/SlotMap

Repository files navigation

Slot Map

License ci codecov

A Slot Map is a high-performance associative container with persistent unique keys to access stored values. It's designed for performance-critical applications where stable references, O(1) operations, and memory efficiency are essential.

Table of Contents

What is a Slot Map?

A Slot Map solves the problem of storing collections of objects that need stable, safe references but have no clear ownership hierarchy. Unlike std::unordered_map where you provide keys, a slot map generates unique keys when you insert values.

Key differences from std::unordered_map:

  • Slot map generates keys for you (no key collisions)
  • Guaranteed O(1) insertion, removal, and access
  • Memory-stable pointers (values never move in memory)
  • Automatic key invalidation when items are removed
  • Efficient memory layout with page-based allocation

Perfect for:

  • Game entities and component systems
  • Resource management (textures, sounds, etc.)
  • Object pools and handle-based systems
  • Any scenario requiring safe, stable references

Key Features

  • O(1) Performance: Insertion, removal, and access are all O(1) in best, worst, and average case
  • Memory Stability: Pointers to values remain valid until explicitly removed
  • Safe References: Keys automatically become invalid when items are removed
  • Memory Efficient: Page-based allocation prevents memory fragmentation
  • Type Safety: Keys are typed to prevent mixing between different slot maps
  • Iteration Support: Both value-only and key-value iteration
  • Custom Allocators: Support for custom memory allocators
  • Header Only: Single header file, easy to integrate

Quick Start

#include "slot_map.h" // Create a slot map for strings dod::slot_map<std::string> strings; // Insert values and get unique keys auto red_key = strings.emplace("Red"); auto green_key = strings.emplace("Green"); auto blue_key = strings.emplace("Blue"); // Access values using keys const std::string* red_value = strings.get(red_key); if (red_value) { printf("Color: %s\n", red_value->c_str()); // Output: Color: Red } // Remove a value strings.erase(green_key); // Check if keys are still valid printf("Green exists: %d\n", strings.has_key(green_key)); // Output: 0 printf("Blue exists: %d\n", strings.has_key(blue_key)); // Output: 1 // Iterate over all values for (const auto& color : strings) { printf("Color: %s\n", color.c_str()); } // Iterate over key-value pairs for (const auto& [key, color] : strings.items()) { printf("Key: %" PRIslotkey ", Color: %s\n", uint64_t(key), color.get().c_str()); }

Building

Requirements

  • C++17 or later
  • CMake 3.10 or later (for building tests)

Header-Only Integration

Simply include the header file in your project:

#include "slot_map/slot_map.h"

Building Tests

git clone https://github.com/SergeyMakeev/SlotMap.git cd SlotMap mkdir build && cd build cmake .. cmake --build . # Run tests ./SlotMapTest01 ./SlotMapTest02 ./SlotMapTest03 ./SlotMapTest04

Custom Memory Allocator

Define custom allocator macros before including the header:

#define SLOT_MAP_ALLOC(sizeInBytes, alignment) your_aligned_alloc(alignment, sizeInBytes) #define SLOT_MAP_FREE(ptr) your_free(ptr) #include "slot_map.h"

API Reference

Core Operations

emplace(Args&&... args) -> key

Constructs element in-place and returns a unique key.

auto key = slot_map.emplace("Hello", "World"); // Construct string from args

get(key k) -> T* / get(key k) const -> const T*

Returns pointer to value or nullptr if key is invalid.

const std::string* value = slot_map.get(key);

has_key(key k) const -> bool

Returns true if the key exists and is valid.

if (slot_map.has_key(key)) { /* key is valid */ }

erase(key k)

Removes element if key exists. Key becomes invalid.

slot_map.erase(key);

pop(key k) -> std::optional<T>

Removes and returns the value if key exists.

auto value = slot_map.pop(key); // Returns optional<T>

Container Operations

size() const -> size_type

Returns number of elements.

empty() const -> bool

Returns true if container is empty.

clear()

Removes all elements but keeps allocated memory. Invalidates all keys.

reset()

Removes all elements and releases memory. Warning: Only call when no keys are in use.

swap(slot_map& other)

Exchanges contents with another slot map.

Iteration

Value iteration

for (const auto& value : slot_map) { // Process value }

Key-value iteration

for (const auto& [key, value] : slot_map.items()) { // Process key and value }

Debug and Statistics

debug_stats() const -> Stats

Returns internal statistics (O(n) complexity).

auto stats = slot_map.debug_stats(); printf("Active items: %u\n", stats.numAliveItems);

Key Types

64-bit Keys (Default)

dod::slot_map<T> // Uses 64-bit keys dod::slot_map64<T> // Explicit 64-bit keys
Component Bits Range
Tag 12 0..4,095
Version 20 0..1,048,575
Index 32 0..4,294,967,295

32-bit Keys

dod::slot_map32<T> // Uses 32-bit keys
Component Bits Range
Tag 2 0..3
Version 10 0..1,023
Index 20 0..1,048,575

Key Operations

auto key = slot_map.emplace(value); // Type-safe: this won't compile // slot_map<int>::key int_key = int_map.emplace(42); // string_map.get(int_key); // Compiler error! // Convert to/from raw numeric type uint64_t raw_key = key; // Implicit conversion slot_map<T>::key restored_key{raw_key}; // Explicit construction

Advanced Features

Tags

Keys can store small amounts of user data:

auto key = slot_map.emplace("Value"); key.set_tag(42); // Store application data auto tag = key.get_tag(); // Retrieve: tag == 42 // Tag limits: // 64-bit keys: 0..4,095 (12 bits) // 32-bit keys: 0..3 (2 bits)

Custom Page Size

Adjust memory allocation granularity:

// Default page size is 4096 elements dod::slot_map<T, dod::slot_map_key64<T>, 8192> large_pages; dod::slot_map<T, dod::slot_map_key64<T>, 1024> small_pages;

Custom Free Indices Threshold

Control when slot indices are reused:

// Default threshold is 64 dod::slot_map<T, dod::slot_map_key64<T>, 4096, 128> conservative_reuse;

Performance

Time Complexity

  • Insertion: O(1) amortized
  • Removal: O(1)
  • Access: O(1)
  • Iteration: O(n) where n is number of alive elements

Memory Characteristics

  • Page-based allocation: 4096 elements per page by default
  • Pointer stability: Values never move once allocated
  • Memory efficiency: Pages are released when all slots become inactive
  • Cache-friendly: Sequential iteration over alive elements only

Implementation Details

Version Overflow Protection

When a slot's version counter reaches maximum value:

  1. The slot is marked as inactive
  2. No new keys will be generated for this slot
  3. Guarantees no key collisions even with version wrap-around

Free Index Management

  • Recently freed slots aren't immediately reused
  • Minimum threshold (default 64) prevents rapid version increments
  • Balances memory usage with collision avoidance

Page Management

  • Elements are allocated in pages (default 4096 elements)
  • Pages are released when all slots in a page become inactive
  • Provides memory stability and prevents fragmentation

Thread Safety

Slot maps are NOT thread-safe. External synchronization is required for:

  • Concurrent access from multiple threads
  • Reader-writer scenarios

For thread-safe usage:

std::shared_mutex mutex; dod::slot_map<T> slot_map; // Reader { std::shared_lock lock(mutex); auto value = slot_map.get(key); } // Writer  { std::unique_lock lock(mutex); auto key = slot_map.emplace(value); }

Examples

Entity Component System

struct Transform { float x, y, z; }; struct Health { int hp, max_hp; }; dod::slot_map<Transform> transforms; dod::slot_map<Health> healths; // Create entity auto entity_id = generate_entity_id(); auto transform_key = transforms.emplace(Transform{0, 0, 0}); auto health_key = healths.emplace(Health{100, 100}); // Store keys in entity register_component(entity_id, transform_key); register_component(entity_id, health_key);

Resource Management

dod::slot_map<Texture> textures; dod::slot_map<Sound> sounds; class ResourceManager { using TextureHandle = dod::slot_map<Texture>::key; using SoundHandle = dod::slot_map<Sound>::key; TextureHandle load_texture(const std::string& path) { return textures.emplace(load_texture_from_file(path)); } const Texture* get_texture(TextureHandle handle) { return textures.get(handle); } };

Object Pool with Versioning

class BulletPool { struct Bullet { float x, y, dx, dy; bool active; }; dod::slot_map<Bullet> bullets; public: using BulletHandle = dod::slot_map<Bullet>::key; BulletHandle spawn(float x, float y, float dx, float dy) { return bullets.emplace(Bullet{x, y, dx, dy, true}); } void update() { for (auto& bullet : bullets) { if (bullet.active) { bullet.x += bullet.dx; bullet.y += bullet.dy; } } } void destroy(BulletHandle handle) { bullets.erase(handle); // Handle becomes invalid automatically } };

References

About

A slot map is a high-performance associative container with persistent unique 32/64 bit keys to access stored values.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 3

  •  
  •  
  •