You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Rea is a lightweight library of data structures implemented in C++11, designed for constant time insertion, erasure, lookup, and fastest possible iteration. Great for using in games or any other software which needs to manage thousands upon thousands of objects.
3
3
4
-
There are 6 data structures included in this library : `slot_map`, `controlled_slot_map`, `versioned_slot_map`, `regulated_slot_map`, `dense_map`
5
-
and `versioned_dense_map`. The two key data structures are `slot_map` and `dense_map`: the other four are only simple variations of those 2.
4
+
There are 6 data structures included in this library : `slot_list`, `controlled_slot_list`, `versioned_slot_list`, `regulated_slot_list`, `slot_map` and `versioned_slot_map`. The two key data structures are `slot_list` and `list_map`: the other four are only simple variations of those 2.
6
5
7
-
# SlotMap
8
-
Use SlotMap when you have to insert, erase, or look up data in constant time, without the need for constantly repeated iteration. If you require all of those things plus fast iteration, use DenseMap.
6
+
# SlotList
7
+
Use SlotList when you have to insert, erase, or look up data in constant time, without the need for constantly repeated iteration. If you require all of those things plus fast iteration, use SlotMap.
9
8
10
9
## Implementation
11
-
SlotMap internally stores its objects in some RandomAccessContainer (by default `std::deque`). Once you erase an object from SlotMap, the slot where that object used to reside becomes available for reuse. The next object you insert will be put in the last emptied slot.
10
+
SlotList internally stores its objects in some RandomAccessContainer (by default `std::deque`). Once you erase an object from SlotList, the slot where that object used to reside becomes available for reuse. The next object you insert will be put in the last emptied slot.
12
11
The internal container will never grow unless all slots are filled. The "Discussion" section shows how to change the
13
12
internal container from `std::deque` to some other container.
14
13
15
-
Whenever you insert a value into the SlotMap you get its id, which you can later use to access that object. "id" is an index with or without a version count in case of the version variatons of SlotMaps and DenseMaps. More on that later on. That index is what allows us to access the objects in constant time.
14
+
Whenever you insert a value into the SlotList you get its id, which you can later use to access that object. "id" is an index with or without a version count in case of the version variatons of SlotLists and DenseMaps. More on that later on. That index is what allows us to access the objects in constant time.
16
15
17
-
While you can use the ids to iterate over all stored objects, it's not advisable to do it repeatedly: you might be jumping all over memory and hence destroying cache locality. As stated above, for iteration use DenseMap.
16
+
While you can use the ids to iterate over all stored objects, it's not advisable to do it repeatedly: you might be jumping all over memory and hence destroying cache locality. As stated above, for iteration use SlotMap.
18
17
19
18
## Usage
20
-
All SlotMaps have the same first, and the last 2 template arguments.
19
+
All SlotLists have the same first, and the last 2 template arguments.
21
20
```cpp
22
-
some_slot_map<T, // value_type
23
-
.
24
-
.
25
-
.
26
-
S = std::size_t, // size_type
27
-
A = std::allocator<T>>; // allocator_type
21
+
some_slot_list<T, // value_type
22
+
.
23
+
.
24
+
.
25
+
S = std::size_t, // size_type
26
+
A = std::allocator<T>>; // allocator_type
28
27
```
29
-
Considering that each slot will store two objects of "size_type" type as well as the single object of "value_type", if you know in advance the maximum size of the container, you might want to restrict how much space slots take up by choosing a smaller "size_type" that can still store that maximum container size.
28
+
Considering that each slot will store two objects of "size_type" type as well as the single object of "value_type", if you know in advance the maximum size of the container, you might want to restrict how much space slots take up by choosing a smaller "size_type" which can still be used to reference all objects in the container.
30
29
31
-
Template arguments for all SlotMap variations are explained below.
30
+
Template arguments for all SlotList variations are explained below.
32
31
33
-
### variation 1 : slot_map
34
-
rea::slot_map acts as a basic SlotMap. It has no additional template arguments, only the ones described above.
32
+
### variation 1 : slot_list
33
+
`rea::slot_list` acts as a basic SlotList. It has no additional template arguments, only the ones described above.
35
34
```cpp
36
-
rea::slot_map<T, // value_type
37
-
S = std::size_t, // size_type
38
-
A = std::allocator<T>> // allocator_type
35
+
rea::slot_list<T, // value_type
36
+
S = std::size_t, // size_type
37
+
A = std::allocator<T>> // allocator_type
39
38
```
40
39
41
40
```cpp
@@ -63,45 +62,45 @@ T get_and_remove_rand_value(std::vector<T> &values) {
63
62
}
64
63
65
64
int main() {
66
-
using sm_type = rea::slot_map<std::pair<int, int>>;
67
-
sm_type sm;
65
+
using sl_type = rea::slot_list<std::pair<int, int>>;
66
+
sl_type sl;
68
67
std::vector<sm_type::id_type> ids;
69
68
ids.reserve(1000);
70
-
sm.reserve(1000);
69
+
sl.reserve(1000);
71
70
72
71
// insertion
73
72
for(size_t i = 0; i < 1000; ++i) {
74
-
ids.push_back(sm.insert({i, i*2}));
73
+
ids.push_back(sl.insert({i, i*2}));
75
74
}
76
75
77
76
// erasure
78
77
for(size_t i = 0; i < 100; ++i) {
79
78
auto id = get_and_remove_rand_value(ids);
80
-
sm.erase(id);
79
+
sl.erase(id);
81
80
}
82
81
83
82
// lookup
84
83
for(auto id : ids)
85
-
std::cout << sm.id_value(id).first << std::endl;
84
+
std::cout << sl.id_value(id).first << std::endl;
86
85
87
86
88
87
// iteration
89
-
for(auto id = sm.id_begin(); !sm.id_is_end(id); id = sm.id_next(id))
90
-
std::cout << sm.id_value(id).first << std::endl;
88
+
for(auto id = sl.id_begin(); !sl.id_is_end(id); id = sl.id_next(id))
89
+
std::cout << sl.id_value(id).first << std::endl;
91
90
}
92
91
```
93
92
94
93
95
-
### variation 2 : controlled_slot_map
94
+
### variation 2 : controlled_slot_list
96
95
Objects which you erase are not destroyed, only "marked" as empty, so they can be reassigned to in the future. If you are storing objects which themselves are holding some resources (e.g. a pointer to some allocated memory) this could be problematic, beacuse you won't be able to release that memory until the entire SlotMap is destroyed or another value is assigned to that object.
97
96
98
-
To solve that issue, controlled_slot_map is introduced. Its second template argument is a functor, which returns a value to be assigned to all empty slots (it's defaulted to a `rea::get_empty` functor which returns a default constructed object).
97
+
To solve that issue, `rea::controlled_slot_list` is introduced. Its second template argument is a functor, which returns a value to be assigned to all empty slots (it's defaulted to a `rea::get_empty` functor which returns a default constructed object).
If, for instance, you are erasing objects inside the SlotMap from 2 different parts of your program, ids might no longer
123
122
point to correct objects, but to either erased or objects filled with different values than what the id originally pointed to.
124
123
125
-
To solve that issue, `versioned_slot_map` is introduced. It takes as a second template argument an IntegralType, which will represent the current version of the slot (default is `std::size_t`). Each time an object is erased, the slot which contains that object increases its version count by 1.
124
+
To solve that issue, `rea::versioned_slot_list` is introduced. It takes as a second template argument an IntegralType, which will represent the current version of the slot (default is `std::size_t`). Each time an object is erased, the slot which contains that object increases its version count by 1.
126
125
```cpp
127
-
rea::versioned_slot_map<T, // value_type
128
-
V = std::size_t, // version_type
129
-
S = std::size_t, // size_type
130
-
A = std::allocator<T>> // allocator_type
126
+
rea::versioned_slot_list<T, // value_type
127
+
V = std::size_t, // version_type
128
+
S = std::size_t, // size_type
129
+
A = std::allocator<T>> // allocator_type
131
130
```
132
131
Here slots also store a "version_type". Choosing different "version_type" may effect size of the slot, and hence the amount of memory needed.
133
132
@@ -136,34 +135,33 @@ Here slots also store a "version_type". Choosing different "version_type" may ef
slot_map and controlled_slot_map also have id_is_valid(id_type id) method defined.
158
+
`rea::slot_list` and `rea::controlled_slot_list` also have `id_is_valid(id_type id)` method defined.
160
159
It's a constexpr function which always returns true.
161
160
162
161
163
162
164
-
### variation 4 : regulated_slot_map
165
-
If you need both the ability to set an empty value and to keep a version count, use regulated_slot_map. Its second template argument
166
-
is a functor which returns an empty value, and its third is an IntegralType to be used for versioning. They are defaulted same as controlled_slot_map and versiond_slot_map.
163
+
### variation 4 : regulated_slot_list
164
+
If you need both the ability to set an empty value and to keep a version count, use `rea::regulated_slot_list`. Its second template argument is functor which returns an empty value, and its third is an IntegralType to be used for versioning. They are defaulted same as `rea::controlled_slot_list` and `rea::versiond_slot_list`.
If like in the SlotMap you need constant time insertion, removal, and lookup, as well as cache friendly iteration through a contiguous array, use DenseMap.
186
+
# SlotMap
187
+
If like in the SlotList you need constant time insertion, removal, and lookup, as well as cache friendly iteration through a contiguous array, use SlotMap.
190
188
191
-
Implementation details are given below, although there is a video which explains exactly what this data structure is. In [the video](https://www.youtube.com/watch?v=SHaAR7XPtNU) this data structure is called SlotMap not DenseMap. If you've seen it, "Implementation" section won't give you any more details and could be skipped.
189
+
Implementation details are given below, although there is a [video](https://www.youtube.com/watch?v=SHaAR7XPtNU) which explains exactly what this data structure is. If you've seen it, "Implementation" section won't give you any more details and could be skipped.
192
190
193
191
## Implementation
194
-
DenseMap is internally implemented as 2 std::vectors and a slot_map like data structure.
192
+
SlotMap is internally implemented as 2 std::vectors and a slot_list like data structure.
195
193
- vector 1 = *ValueContainer*;
196
194
- vector 2 = *IDPosContainer*;
197
-
- slot_map = *IDSlotContainer*;
195
+
- slot_list = *IDSlotContainer*;
198
196
199
-
*ValueContainer* stores objects of type "value_type" of the DenseMap, and just like any other vector they are stored contiguously. The *IDSlotContainer* stores indices which point to an object inside the *ValueContainer*. Once an object is erased, last object inside the *ValueContainer* is moved into its place, hence all objects remain densely packed at the cost of not preserving order. Slot of the *IDSlotContainer* which points to the erased object becomes available for reuse.
197
+
*ValueContainer* stores objects of type "value_type" of the SlotMap, and just like any other vector they are stored contiguously. The *IDSlotContainer* stores indices which point to an object inside the *ValueContainer*. Once an object is erased, last object inside the *ValueContainer* is moved into its place, hence all objects remain densely packed at the cost of not preserving order. Slot of the *IDSlotContainer* which points to the erased object becomes available for reuse.
200
198
201
199
Now we have a problem though. The slot which pointed to the last object inside *ValueContainer* now points to past the end object. In order to find that slot and update it to point to a new location, we introduce the *IDPosContainer*.
202
200
203
201
*IDPosContainer* stores indices of *IDSlotContainer* slots, which correspond to objects stored *ValueContainer*. E.g., third object of *IDPosContainer* is an index of an *IDSlotContainer* slot, and that slot points to the third object of *ValueContainer*. Once past the end object is moved to the erased location, its index inside *IDSlotContainer* is also moved to the corresponding location of *IDSlotContainer*. In that way all lookup operations are done in constant time.
204
202
205
203
## Usage
206
-
As stated earlier the main difference between the SlotMap and the DenseMap is in iteration. It's not possible to iterate through the objects stored in DenseMap using their ids. IDs can only be used for lookup. For iteration regular RandomAccess iterators are used (by default std::vector::iterator, as with SlotMap you can change the internal containers, "Discussion" section shows how to do that).
204
+
As stated earlier the main difference between the SlotList and the SlotMap is in iteration. It's not possible to iterate through the objects stored in SlotMap using their ids. IDs can only be used for lookup. For iteration regular RandomAccess iterators are used (by default `std::vector::iterator`, as with SlotList you can change the internal containers, "Discussion" section shows how to do that).
207
205
208
-
Considering all of the users objects are kept in a contiguous array, and all erased objects are destructed, there is no need for controlled or regulated version of DenseMap.
206
+
Considering all of the users objects are kept in a contiguous array, and all erased objects are destructed, there is no need for controlled or regulated version of SlotMap.
209
207
210
-
All DenseMaps, just like all SlotMaps, have the same first, and the last 2 template arguments.
208
+
All SlotMaps, just like all SlotLists, have the same first, and the last 2 template arguments.
211
209
```cpp
212
-
some_dense_map<T, // value_type
213
-
.
214
-
.
215
-
.
216
-
S = std::size_t, // size_type
217
-
A = std::allocator<T>>; // allocator_type
210
+
some_slot_map<T, // value_type
211
+
.
212
+
.
213
+
.
214
+
S = std::size_t, // size_type
215
+
A = std::allocator<T>>; // allocator_type
218
216
```
219
217
220
-
### variation 1 : dense_map
221
-
`rea::dense_map` acts as a basic DenseMap. It has no additional template arguments, only the ones described above.
218
+
### variation 1 : slot_map
219
+
`rea::slot_map` acts as a basic SlotMap. It has no additional template arguments, only the ones described above.
auto id = get_and_remove_rand_value(ids); //this function is defined in slot map code above
248
-
dm.erase(id);
246
+
sm.erase(id);
249
247
}
250
248
251
249
// lookup
@@ -264,19 +262,17 @@ int main() {
264
262
// "ids" are know in value iteration order
265
263
}
266
264
```
267
-
### variation 2 : versioned_dense_map
268
-
`rea::versioned_dense_map` is to `rea::dense_map`, what `rea::versioned_slot_map` is to `rea::slot_map`. It keeps a version count for you.
269
-
And just like for `rea::versioned_slot_map`, `rea::versioned_dense_map::id_is_valid(id_type)` method may return false if the version counts dont match.
265
+
### variation 2 : versioned_slot_map
266
+
`rea::versioned_slot_map` is to `rea::slot_map`, what `rea::versioned_slot_list` is to `rea::slot_list`. It keeps a version count for you. And just like for `rea::versioned_slot_list`, `rea::versioned_slot_map::id_is_valid(id_type)` method may return false if the version counts dont match.
270
267
```cpp
271
-
rea::versioned_dense_map<T, // value_type
272
-
V = std::size_t, // version_type
273
-
S = std::size_t, // size_type
274
-
A = std::allocator<T>> // allocator_type
268
+
rea::versioned_slot_map<T, // value_type
269
+
V = std::size_t, // version_type
270
+
S = std::size_t, // size_type
271
+
A = std::allocator<T>> // allocator_type
275
272
```
276
273
# Installation
277
274
Include "rea.h" header file in your project, and you're ready to go. It should work with any C++11 compliant compiler.
278
275
279
276
# Discussion
280
277
Discussion section will be added shortly in the future.
0 commit comments