Difference between revisions of "Object pool"

From GTAMods Wiki
Jump to navigation Jump to search
m (Gave the appropriate category.)
m (Improved readability.)
 
Line 4: Line 4:
 
In trilogy chapters, pools come with (note numerical fields are all 32-bit signed integers):
 
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;
+
* <code>object_list</code>, an array of variable-sized slots of same type;
* ''byte-map'', an array of slot states each of which holds a ''seed'' (in the lower 7 bits) to randomize handles (also known as references) by a conspicuous factor and a ''free-flag'' (in the sign-bit, 8th bit) to signal the emptiness of the slot itself;
+
* <code>byte_map</code>, an array of slot states each of which holds a <code>seed</code> (in the lower 7 bits) to randomize handles (also known as references) by a conspicuous factor and a <code>free_flag</code> (in the sign-bit, 8th bit) to signal the emptiness of the slot itself;
* ''count'', the number of slots;
+
* <code>count</code>, the number of slots;
* ''first-free'', a hint where to look from for free slot searching;
+
* <code>first_free</code>, a hint where to look from for free slot searching;
* ''initialized-state'', a flag indicating if arrays were allocated.
+
* <code>initialized_state</code>, a flag indicating if arrays were allocated.
  
 
Pools are commonly implemented in the following way:
 
Pools are commonly implemented in the following way:
  
* On pool initialization &ndash; <font face="Courier New">Initialise(count)</font>:
+
* On pool initialization &ndash; <code>Initialise(count)</code>:
** ''object-list'' and ''byte-map'' are allocated with a fixed number of slots;
+
** <code>object_list</code> and <code>byte_map</code> are allocated with a fixed number of slots;
** ''count'' is set accordingly while ''first-free'' becomes -1;
+
** <code>count</code> is set accordingly while <code>first_free</code> becomes <code>-1</code>;
** ''initialized-state'' is switched to true and ''byte-map'' is cleared (see below).
+
** <code>initialized_state</code> is switched to true and <code>byte_map</code> is cleared as below.
* On pool clean up &ndash; <font face="Courier New">Clear()</font>:
+
* On pool clean up &ndash; <code>Clear()</code>:
** For each slot of ''byte-map'', ''seed'' is zeroed while ''free-flag'' is set.
+
** For each slot of <code>byte_map</code>, <code>seed</code> is zeroed while <code>free_flag</code> is set.
* On pool shutdown &ndash; <font face="Courier New">Flush()</font>:
+
* On pool shutdown &ndash; <code>Flush()</code>:
** If ''count'' isn't equal to 0:
+
** If <code>count</code> isn't equal to 0:
*** If ''initialized-state'' is true, ''object-list'' and ''byte-map'' are deallocated and zeroed (null-pointer check involved);
+
*** If <code>initialized_state</code> is true, <code>object_list</code> and <code>byte_map</code> are deallocated and zeroed (null-pointer check involved);
*** ''count'' and ''first-free'' are zeroed.
+
*** <code>count</code> and <code>first_free</code> are zeroed.
* On slot lookup &ndash; <font face="Courier New">GetAt(index)</font>:
+
* On slot lookup &ndash; <code>GetSlot(index) -> object</code>:
** ''free-flag'' nullity of ''byte-map[index]'' is verified:
+
** <code>free_flag</code> nullity of <code>byte_map[index]</code> is verified:
*** If true, ''object-list[index]'' is returned;
+
*** If true, the reference of <code>object_list[index]</code> is returned;
 
*** If false, a null-pointer is returned.
 
*** If false, a null-pointer is returned.
* On slot lookup &ndash; <font face="Courier New">GetAtHandle(handle)</font>:
+
* On slot lookup &ndash; <code>GetAt(handle) -> object</code>:
** ''index'' is extracted from ''handle'' by <code>index = handle >> 8</code> and ''byte-map[index]'' is compared to the lower 8 bits of ''handle'':
+
** <code>index</code> is extracted from <code>handle</code> by <code>index = handle >> 8</code> and <code>byte_map[index]</code> is compared to the lower 8 bits of <code>handle</code>:
*** If equal, ''object-list[index]'' is returned;
+
*** If equal, the reference of <code>object_list[index]</code> is returned;
 
*** If different, a null-pointer is returned.
 
*** If different, a null-pointer is returned.
* On slot just-index retrieval &ndash; <font face="Courier New">GetJustIndex(object)</font>:
+
* On slot index retrieval &ndash; <code>GetJustIndex(object) -> index</code>:
** ''slot-id'' is returned by <code>slot_id = (object - object_list) / sizeof(object)</code>.
+
** <code>index</code> is returned by <code>index = (object - object_list) / sizeof(object)</code>.
* On slot index retrieval &ndash; <font face="Courier New">GetIndex(object)</font>:
+
* On slot reference index retrieval &ndash; <code>GetIndex(object) -> handle</code>:
** ''slot-id'' is computed as above and ''handle'' is returned by <code>handle = (slot_id << 8) | byte_map[slot_id]</code>.
+
** <code>index</code> is computed as above and <code>handle</code> is returned by <code>handle = (index << 8) | byte_map[index]</code>.
* On free-slot request &ndash; <font face="Courier New">New()</font>:
+
* On free slot request &ndash; <code>New() -> object</code>:
 
** Inside loop:
 
** Inside loop:
*** ''first-free'' is incremented at each iteration;
+
*** <code>first_free</code> is incremented at each iteration;
*** If ''count'' is reached, ''first-free'' is zeroed and the loop continues below, till the top one more time again (pretty odd, stopping at initial ''first-free +1'' would have made more sense);
+
*** If <code>count</code> is reached, <code>first_free</code> is zeroed and the loop continues below, till the top one more time again (pretty odd, stopping at initial <code>first_free + 1</code> would have made more sense);
*** ''free-flag'' of ''byte-map[first-free]'' is verified:
+
*** <code>free_flag</code> of <code>byte_map[first_free]</code> is verified:
**** If true, ''free-flag'' is unset while ''seed'' is advanced (overflow is solved by <code>seed = (seed + 1) % 128</code>) and the reference of ''object-list[first-free]'' is returned;
+
**** If true, <code>free_flag</code> is unset while <code>seed</code> is advanced (overflow is solved by <code>seed = (seed + 1) % 128</code>) and the reference of <code>object_list[first_free]</code> is returned;
 
**** If false, the loop continues to the next iteration.
 
**** If false, the loop continues to the next iteration.
 
** Outside loop:
 
** Outside loop:
*** No free slot was found, hence null-pointer returned.
+
*** No free slot was found, hence a null-pointer is returned.
* On slot reservation &ndash; <font face="Courier New">New(handle)</font>:
+
* On slot reservation &ndash; <code>New(handle) -> object</code>:
** ''index'' is extracted from ''handle'' (see above) while ''free-flag'' and ''seed'' of ''byte-map[index]'' are respectively unset and advanced (overflow solved as mentioned);
+
** <code>index</code> is extracted from <code>handle</code> while <code>free_flag</code> and <code>seed</code> of <code>byte_map[index]</code> are respectively unset and advanced (overflow solved as aforementioned);
** ''first-free'' is then set to ''index'' of first ''byte-map[index]'' with ''free-flag'' unset.
+
** <code>first_free</code> is then zeroed and incremented till <code>byte_map[first_free]</code> with <code>free_flag</code> unset;
* On slot removal &ndash; <font face="Courier New">Delete(object)</font>:
+
** Lastly, the reference of <code>object_list[first_free]</code> is returned.
** ''slot-id'' is obtained as above while ''free-flag'' of ''byte-map[slot-id]'' is set;
+
* On slot removal &ndash; <code>Delete(object)</code>:
** If ''index'' is less than ''first-free'', the latter is updated.
+
** <code>index</code> is extracted from <code>object</code> while <code>free_flag</code> of <code>byte_map[index]</code> is set;
 +
** If <code>index</code> is lesser than <code>first_free</code>, the latter is updated.
  
 
==Handle uniqueness==
 
==Handle uniqueness==
  
The probability to encounter an object with a non-unique handle after an undefined amount of new/delete requests is given by...<br/><code>cost = 1 / ((free_slot_count * 128) + busy_slot_count)</code>:
+
The probability to encounter an object with a non-unique handle after an undefined amount of new/delete requests is given by <code>cost = 1 / ((free_slot_count * 128) + busy_slot_count)</code>.<br/>
* In the worst case (assuming the last reserved object of a full pool is repeatedly released/re-added), it is <code>cost = 1 / (128 + (object_count - 1))</code>;
+
In the worst case (assuming the last reserved object of a full pool is repeatedly released/re-added), it is <code>cost = 1 / (128 + (object_count - 1))</code>.<br/>
* In the best case (assuming an empty pool is filled till the maximum with no release), it is <code>cost = 1 / (object_count * 128)</code>.
+
In the best case (assuming an empty pool is filled up to the maximum with no release), it is <code>cost = 1 / (object_count * 128)</code>.
  
 
==See also==
 
==See also==
  
* {{GTAF|777350|<nowiki>[TUT|DOC] SCM functions (including TraversePoolEntities algorithms)</nowiki>}} &ndash; Multi-purpose tutorial also showing a scripted implementation of pool traversal to catch any type of entity by handle.
+
* {{GTAF|777350|<nowiki>[TUT|DOC]</nowiki> SCM functions (including TraversePoolEntities algorithms)}} &ndash; Multi-purpose tutorial also showing a scripted implementation of pool traversal to catch any type of entity by handle.
  
 
[[Category:Documentation]]
 
[[Category:Documentation]]

Latest revision as of 17:41, 28 April 2021

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 a seed (in the lower 7 bits) to randomize handles (also known as references) by a conspicuous factor and a free_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_list and byte_map are allocated with a fixed number of slots;
    • count is set accordingly while first_free becomes -1;
    • initialized_state is switched to true and byte_map is cleared as below.
  • On pool clean up – Clear():
    • For each slot of byte_map, seed is zeroed while free_flag is set.
  • On pool shutdown – Flush():
    • If count isn't equal to 0:
      • If initialized_state is true, object_list and byte_map are deallocated and zeroed (null-pointer check involved);
      • count and first_free are zeroed.
  • On slot lookup – GetSlot(index) -> object:
    • free_flag nullity of byte_map[index] is verified:
      • If true, the reference of object_list[index] is returned;
      • If false, a null-pointer is returned.
  • On slot lookup – GetAt(handle) -> object:
    • index is extracted from handle by index = handle >> 8 and byte_map[index] is compared to the lower 8 bits of handle:
      • If equal, the reference of object_list[index] is returned;
      • If different, a null-pointer is returned.
  • On slot index retrieval – GetJustIndex(object) -> index:
    • index is returned by index = (object - object_list) / sizeof(object).
  • On slot reference index retrieval – GetIndex(object) -> handle:
    • index is computed as above and handle is returned by handle = (index << 8) | byte_map[index].
  • On free slot request – New() -> object:
    • Inside loop:
      • first_free is incremented at each iteration;
      • If count is reached, first_free is zeroed and the loop continues below, till the top one more time again (pretty odd, stopping at initial first_free + 1 would have made more sense);
      • free_flag of byte_map[first_free] is verified:
        • If true, free_flag is unset while seed is advanced (overflow is solved by seed = (seed + 1) % 128) and the reference of object_list[first_free] is returned;
        • If false, the loop continues to the next iteration.
    • Outside loop:
      • No free slot was found, hence a null-pointer is returned.
  • On slot reservation – New(handle) -> object:
    • index is extracted from handle while free_flag and seed of byte_map[index] are respectively unset and advanced (overflow solved as aforementioned);
    • first_free is then zeroed and incremented till byte_map[first_free] with free_flag unset;
    • Lastly, the reference of object_list[first_free] is returned.
  • On slot removal – Delete(object):
    • index is extracted from object while free_flag of byte_map[index] is set;
    • If index is lesser than first_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