stable_id/
lib.rs

1/*!
2This crate mainly deals with issuing and maintaining stability of indices.
3It provides 4 structs and each helps in different area.
4
5This library was created for my game development endeavor.
6Not going great on that front as I kept restarting the project.
7However, I saw these utility structures coming back multiple times so I'm making a crate for them.
8
9In version 0.2.0, you can supply custom Id tuple structs that are based on unsigned integers (from 8bit to 64bits).
10The id type needs to be derived with the following:
11```
12// Minimal needed for all traits that are introduced by this crate.
13#[derive(derive_stable_id::StableId)]
14struct Id(u8);
15
16
17// These are needed under normal circumstances.
18#[derive(derive_stable_id::StableId)]
19struct Id32(u32);
20
21let x: stable_id::Eids<Id32> = Default::default();
22let x: stable_id::Sequence<Id32> = Default::default();
23let x: stable_id::SparseEntities<Id32, String> = Default::default();
24let x: stable_id::Entities<Id32, String> = Default::default();
25let x: stable_id::Tec<Id32, String> = Default::default();
26```
27
28# Use cases
29| Struct                | Type          | Suggestion    | Description |
30| -----------           | ----          | ----          |-----------  |
31| [`Eids`]              | Id            | Dense data    | You want a way to create ids, and **do** care about recovering ids. |
32| [`Sequence`]          | Id            | Sparse data   | You want a way to create ids, and **don't** care about recovering ids, but you don't want to use the HashMap-based [`Entities`] struct. |
33| [`Entities`]          | Collection    | Dense data    | The go-to collection of this library.
34| [`SparseEntities`]    | Collection    | Sparse data   | You want mix sequence (ids not recycled) and HashMap together. |
35| [`Tec`]               | Collection    | Dense data    | You want to use a vec to store data, but need constant entity removal. [`Tec`] reclaims the spaces for you as you insert more new items.
36 */
37use std::collections::BTreeSet;
38
39use rustc_hash::FxHashMap;
40
41pub use derive_stable_id::StableId;
42pub use stable_id_traits::*;
43
44mod eids;
45mod entities;
46mod sequence;
47mod sparse_entities;
48mod tomb_vec;
49
50/**
51Stands for Entity Id generator (ids are redeemable).
52Basically a counter with a B-tree based free "set" (list).
53
54# Use case
55- you want to recycle ids due to frequent entity removal
56- you want to use custom data structure but need id management
57- ids start from zero
58
59# Example
60```
61use stable_id::Eids;
62
63let mut entities: Eids<u8> = Default::default();
64let id = entities.claim();
65entities.unclaim(id);
66```
67
68See [`Self::coalesce()`] if you want to pack ids together, like when you're trying to tighten up an array and
69saving it into a database/save file (i.e. when game players are saving their progress).
70*/
71#[derive(Clone, Default)]
72pub struct Eids<IndexT>
73where
74    IndexT: Ord,
75{
76    freed: BTreeSet<IndexT>,
77    next: IndexT,
78}
79
80/**
81An abstracted monotonically increasing counter structure.
82Once you claim an id you can't go back.
83
84# Example
85```
86use stable_id::Sequence;
87
88let mut s: Sequence<u8> = Default::default();
89assert_eq!(s.next_value(), 0);
90assert_eq!(s.next_value(), 1);
91assert_eq!(s.next_value(), 2);
92
93let mut s = Sequence::continue_from(1234u16);
94assert_eq!(s.next_value(), 1234);
95assert_eq!(s.next_value(), 1235);
96assert_eq!(s.next_value(), 1236);
97```
98 */
99#[derive(Clone, Default)]
100pub struct Sequence<IndexT> {
101    counter: IndexT,
102}
103
104/// inspired by https://github.com/fitzgen/generational-arena/blob/72975c8355949c2338976d944e047c9d9f447174/src/lib.rs#L178
105/// but without the generation stuff.
106#[derive(Clone, Debug)]
107pub(crate) enum Slot<DataT, IndexT> {
108    Dead { next_free: IndexT },
109    Alive(DataT),
110}
111
112/**
113Short for [tombstone](https://en.wikipedia.org/wiki/Tombstone_(programming))-based vector.
114Inspired by [generational-arena](https://github.com/fitzgen/generational-arena/blob/72975c8355949c2338976d944e047c9d9f447174/src/lib.rs#L178), but without the generation stuff.
115
116# Features
117- index stability when deleting an element
118- maintain freed list, and is basically free for large structs
119
120Use case: you have compact data that needs to be inserted & deleted while other objects maintain their index-based references.
121
122Don't use it if:
123- the data are sparse (use a HashMap or [`Entities`] instead)
124- you don't need to remove data (use a Vec **with** [`Sequence`] instead)
125
126```
127use stable_id::Tec;
128
129// use the `derive_more` crate to shortern the list
130#[derive(derive_stable_id::StableId, Debug)]
131struct Id(u8);
132struct Data { field: usize }
133
134let mut storage: Tec<Id, Data> = Default::default();
135assert_eq!(storage.alloc(Data {field: 123}), Id(0));
136assert_eq!(storage.get(Id(0)).unwrap().field, 123);
137```
138*/
139#[derive(Clone)]
140pub struct Tec<IndexT, DataT> {
141    vec: Vec<Slot<DataT, IndexT>>,
142    /// invariants: the free index must be either
143    ///      - pointer some dead slot within the `vec`
144    ///      - or the sentinal value of Maximum::maximum()
145    /// In other words, the `vec` cannot have trailing dead slots
146    next_free: IndexT,
147    count: usize,
148}
149
150/**
151This is a sandwich of HashMap and [`Sequence`].
152
153# Features:
154- stable indices and not redeemable
155- generated indices
156
157Use cases:
158- you're removing more entities than you are adding
159- you don't care about relaiming ids
160*/
161pub struct SparseEntities<IndexT, DataT> {
162    data: FxHashMap<IndexT, DataT>,
163    seq: Sequence<IndexT>,
164}
165
166/**
167This is a lazily memory-compact version of [`SparseEntities`].
168Use cases are the same but there are different tradeoffs.
169
170
171# Tradeoff vs [`SparseEntities`].
172- this struct uses a hash-based virtual table to translate issued ids into an id used internally by its backing collection [`Tec`].
173  So accessing items should be similar -- it's dictated by HashMap's access complexity, since once it finds
174  the internal id, a random access follows.
175- removing items is O([`Tec::remove()`]) = O(n lg n) though I have plans to make it O(n). An added benefits is [`remove()`] will also
176  try to compact the memory by removing dead slots from [`Tec`] when there's a majority of dead slots -- it's another O(n) pass.
177*/
178#[derive(Clone)]
179pub struct Entities<IndexT, DataT> {
180    vtable: FxHashMap<IndexT, IndexT>, // virtual id -> physical id
181    data: Tec<IndexT, DataT>,
182    seq: Sequence<IndexT>,
183}