Object pool
The object pool is a design pattern capable of reducing the allocation/deallocation overhead by reusing pre-instanced resources on demand and restricting their simultaneous usage at a given time. It is heavily employed when the bottleneck induced by the frequent creations/destructions of objects is a concern.
Practical implementation
In trilogy chapters, pools come with (note numerical fields are all 32-bit signed integers):
object_list, an array of variable-sized slots of same type;byte_map, an array of slot states each of which holds aseed(in the lower 7 bits) to randomize handles (also known as references) by a conspicuous factor and afree_flag(in the sign-bit, 8th bit) to signal the emptiness of the slot itself;count, the number of slots;first_free, a hint where to look from for free slot searching;initialized_state, a flag indicating if arrays were allocated.
Pools are commonly implemented in the following way:
- On pool initialization –
Initialise(count):object_listandbyte_mapare allocated with a fixed number of slots;countis set accordingly whilefirst_freebecomes-1;initialized_stateis switched to true andbyte_mapis cleared as below.
- On pool clean up –
Clear():- For each slot of
byte_map,seedis zeroed whilefree_flagis set.
- For each slot of
- On pool shutdown –
Flush():- If
countisn't equal to 0:- If
initialized_stateis true,object_listandbyte_mapare deallocated and zeroed (null-pointer check involved); countandfirst_freeare zeroed.
- If
- If
- On slot lookup –
GetSlot(index) -> object:free_flagnullity ofbyte_map[index]is verified:- If true, the reference of
object_list[index]is returned; - If false, a null-pointer is returned.
- If true, the reference of
- On slot lookup –
GetAt(handle) -> object:indexis extracted fromhandlebyindex = handle >> 8andbyte_map[index]is compared to the lower 8 bits ofhandle:- If equal, the reference of
object_list[index]is returned; - If different, a null-pointer is returned.
- If equal, the reference of
- On slot index retrieval –
GetJustIndex(object) -> index:indexis returned byindex = (object - object_list) / sizeof(object).
- On slot reference index retrieval –
GetIndex(object) -> handle:indexis computed as above andhandleis returned byhandle = (index << 8) | byte_map[index].
- On free slot request –
New() -> object:- Inside loop:
first_freeis incremented at each iteration;- If
countis reached,first_freeis zeroed and the loop continues below, till the top one more time again (pretty odd, stopping at initialfirst_free + 1would have made more sense); free_flagofbyte_map[first_free]is verified:- If true,
free_flagis unset whileseedis advanced (overflow is solved byseed = (seed + 1) % 128) and the reference ofobject_list[first_free]is returned; - If false, the loop continues to the next iteration.
- If true,
- Outside loop:
- No free slot was found, hence a null-pointer is returned.
- Inside loop:
- On slot reservation –
New(handle) -> object:indexis extracted fromhandlewhilefree_flagandseedofbyte_map[index]are respectively unset and advanced (overflow solved as aforementioned);first_freeis then zeroed and incremented tillbyte_map[first_free]withfree_flagunset;- Lastly, the reference of
object_list[first_free]is returned.
- On slot removal –
Delete(object):indexis extracted fromobjectwhilefree_flagofbyte_map[index]is set;- If
indexis lesser thanfirst_free, the latter is updated.
Handle uniqueness
The probability to encounter an object with a non-unique handle after an undefined amount of new/delete requests is given by cost = 1 / ((free_slot_count * 128) + busy_slot_count).
In the worst case (assuming the last reserved object of a full pool is repeatedly released/re-added), it is cost = 1 / (128 + (object_count - 1)).
In the best case (assuming an empty pool is filled up to the maximum with no release), it is cost = 1 / (object_count * 128).
See also
GTAForums: [TUT|DOC] SCM functions (including TraversePoolEntities algorithms) – Multi-purpose tutorial also showing a scripted implementation of pool traversal to catch any type of entity by handle.