• Stars
    star
    246
  • Rank 164,726 (Top 4 %)
  • Language
    C++
  • License
    MIT License
  • Created almost 3 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

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

Slot Map

Actions Status Build status codecov MIT

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 dod::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

Implementation details

The slot map container will allocate memory in pages (default page size = 4096 elements) to avoid memory spikes during growth and be able to deallocate pages that are no longer needed. Also, the page-based memory allocator is very important since it guarantees "pointers stability"; hence, we never move values in memory.

Keys are always uses uint64_t/uint32_t (configurable) and technically typless, but we "artificially" make them typed to get a few extra compile-time checks.
i.e., the following code will produce a compiler error

slot_map<std::string> strings;
slot_map<int> numbers;
slot_map<int>::key numKey = numbers.emplace(3);
const std::string* value = strings.get(numKey);   //  <---- can not use slot_map<int>::key to index slot_map<std::string> !

The keys can be converted to/from their numeric types if you do not need additional type checks.

slot_map<int> numbers;
slot_map<int>::key numKey = numbers.emplace(3);
uint64_t rawKey = numKey;  // convert to numeric type (like cast pointer to void*)
...
slot_map<int>::key numKey2{rawKey}; // create key from numeric type

When a slot is reused, its version is automatically incremented (to invalidate all existing keys that refers to the same slot). But since we only use 20-bits (10-bits for 32 bit keys) for version counter, there is a possibility that the version counter will wrap around, and a new item will get the same key as a removed item.

To mitigate this potential issue, 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 recently freed slot-indices as long as their number is below a certain threshold (kMinFreeIndices = 64).

Keys also can carry a few extra bits of information provided by a user that we called tag.
That might be handy to add application-specific data to keys.

For example:

  slot_map<std::string> strings;
  auto red = strings.emplace("Red");
  red.set_tag(13);
  
  auto tag = red.get_tag();
  assert(tag == 13);

Here is how a key structure looks like internally

64-bit key type

Component Number of bits
tag 12
version 20 (0..1,048,575
index 32 (0..4,294,967,295)

32-bit key type

Component Number of bits
tag 2
version 10 (0..1023)
index 20 (0..1,048,575)

Note: To use your custom memory allocator define SLOT_MAP_ALLOC/SLOT_MAP_FREE before including "slot_map.h"

#define SLOT_MAP_ALLOC(sizeInBytes, alignment) aligned_alloc(alignment, sizeInBytes)
#define SLOT_MAP_FREE(ptr) free(ptr)
#include "slot_map.h"

API

bool has_key(key k) const noexcept
Returns true if the slot map contains a specific key

void reset()
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 clear()
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)

const T* get(key k) const noexcept
If key exists returns a const pointer to the value corresponding to the given key or returns null elsewere.

T* get(key k)
If key exists returns a pointer to the value corresponding to the given key or returns null elsewere.

key emplace(Args&&... args)
Constructs element in-place and returns a unique key that can be used to access this value.

void erase(key k)
Removes element (if such key exists) from the slot map.

std::optional<T> pop(key k)
Removes element (if such key exists) from the slot map, returning the value at the key if the key was not previously removed.

bool empty() const noexcept
Returns true if the slot map is empty.

size_type size() const noexcept
Returns the number of elements in the slot map.

void swap(slot_map& other) noexcept
Exchanges the content of the slot map by the content of another slot map object of the same type.

slot_map(const slot_map& other)
Copy constructor

slot_map& operator=(const slot_map& other)
Copy assignment

slot_map(slot_map&& other) noexcept
Move constructor

slot_map& operator=(slot_map&& other) noexcept
Move asignment

const_values_iterator begin() const noexcept
const_values_iterator end() const noexcept
Const values iterator

for (const auto& value : slotMap)
{
 ...
}

Items items() const noexcept
Const key/value iterator

for (const auto& [key, value] : slotMap.items())
{
...
}

References

Sean Middleditch
Data Structures for Game Developers: The Slot Map, 2013
https://web.archive.org/web/20180121142549/http://seanmiddleditch.com/data-structures-for-game-developers-the-slot-map/

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

Niklas Gray
Data Structures Part 1: Bulk Data, 2019
https://ourmachinery.com/post/data-structures-part-1-bulk-data/

More Repositories

1

TaskScheduler

Cross-platform, fiber-based, multi-threaded task scheduler designed for video games.
C++
557
star
2

ArcadeCarPhysics

Arcade Car Physics - Vehicle Simulation for Unity3D
C#
327
star
3

smmalloc

Blazing fast memory allocator designed for video games
C++
239
star
4

ecs

Thoughts about entity-component-system
C++
183
star
5

Zmeya

Zmeya is a header-only C++11 binary serialization library designed for games and performance-critical applications
C++
102
star
6

Goofy

Goofy - Realtime DXT1/ETC1 encoder
C++
70
star
7

Quaternions-Revisited

Sample code for a 'Quaternions revisited' article from GPU Pro 5
C++
31
star
8

Sandworm

Embeddable preprocessor based on clang. Created to compile ubershaders, but can preprocess anything you like.
C++
20
star
9

GpuZen2

Sample code for the article 'Real-Time Layered Materials Compositing Using Spatial Clustering Encoding'
C#
17
star
10

z8

Z8 : fantasy 8-bit system
JavaScript
11
star
11

Ninja-Ripper-Maya-Importer-

Ninja Ripper Importer for Autodesk Maya
Python
11
star
12

tony-mc-mapface-fuse

A cool-headed display transform - ported to DaVinci Resolve
10
star
13

FastFiberIdea

C++
9
star
14

StaticVector

C++
5
star
15

sm_hash_map

C++
5
star
16

AimExplorer

Aim Acceleration Explorer in FPS games
HTML
4
star
17

DxtSplitter

C++
3
star
18

RobloxAvatarExporter

Python
3
star
19

FixMultimonWindows

C++
3
star
20

CellularAutomata

GPU Cellular Automata fun
C++
3
star
21

ECalc

Expression Calculator (Similar to Far Manager plugin)
HTML
2
star
22

DominantColorsExtractor

Python
2
star
23

Sputnik

Python
2
star
24

GitCheatSheet

2
star
25

BuildConfigurationTool

C#
1
star
26

AT-Linker

C++
1
star
27

sergeymakeev.github.io

github pages
JavaScript
1
star
28

ExcaliburHash

C++
1
star
29

XMQ_Client

Example of XMQ client.
C++
1
star
30

MOD_XMQ

Custom extension for XMPP protocol to provide message broker features.
Erlang
1
star