🛠️🐜 Antkeeper superbuild with dependencies included https://antkeeper.com
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

238 lines
7.4 KiB

  1. # Crash Course: resource management
  2. <!--
  3. @cond TURN_OFF_DOXYGEN
  4. -->
  5. # Table of Contents
  6. * [Introduction](#introduction)
  7. * [The resource, the loader and the cache](#the-resource-the-loader-and-the-cache)
  8. <!--
  9. @endcond TURN_OFF_DOXYGEN
  10. -->
  11. # Introduction
  12. Resource management is usually one of the most critical part of a software like
  13. a game. Solutions are often tuned to the particular application. There exist
  14. several approaches and all of them are perfectly fine as long as they fit the
  15. requirements of the piece of software in which they are used.<br/>
  16. Examples are loading everything on start, loading on request, predictive
  17. loading, and so on.
  18. `EnTT` doesn't pretend to offer a _one-fits-all_ solution for the different
  19. cases. Instead, it offers a minimal and perhaps trivial cache that can be useful
  20. most of the time during prototyping and sometimes even in a production
  21. environment.<br/>
  22. For those interested in the subject, the plan is to improve it considerably over
  23. time in terms of performance, memory usage and functionalities. Hoping to make
  24. it, of course, one step at a time.
  25. # The resource, the loader and the cache
  26. There are three main actors in the model: the resource, the loader and the
  27. cache.
  28. The _resource_ is whatever users want it to be. An image, a video, an audio,
  29. whatever. There are no limits.<br/>
  30. As a minimal example:
  31. ```cpp
  32. struct my_resource { const int value; };
  33. ```
  34. A _loader_ is a class the aim of which is to load a specific resource. It has to
  35. inherit directly from the dedicated base class as in the following example:
  36. ```cpp
  37. struct my_loader final: entt::resource_loader<my_loader, my_resource> {
  38. // ...
  39. };
  40. ```
  41. Where `my_resource` is the type of resources it creates.<br/>
  42. A resource loader must also expose a public const member function named `load`
  43. that accepts a variable number of arguments and returns a shared pointer to a
  44. resource.<br/>
  45. As an example:
  46. ```cpp
  47. struct my_loader: entt::resource_loader<my_loader, my_resource> {
  48. std::shared_ptr<my_resource> load(int value) const {
  49. // ...
  50. return std::shared_ptr<my_resource>(new my_resource{ value });
  51. }
  52. };
  53. ```
  54. In general, resource loaders should not have a state or retain data of any type.
  55. They should let the cache manage their resources instead.<br/>
  56. As a side note, base class and CRTP idiom aren't strictly required with the
  57. current implementation. One could argue that a cache can easily work with
  58. loaders of any type. However, future changes won't be breaking ones by forcing
  59. the use of a base class today and that's why the model is already in its place.
  60. Finally, a cache is a specialization of a class template tailored to a specific
  61. resource:
  62. ```cpp
  63. using my_resource_cache = entt::resource_cache<my_resource>;
  64. // ...
  65. my_resource_cache cache{};
  66. ```
  67. The idea is to create different caches for different types of resources and to
  68. manage each one independently in the most appropriate way.<br/>
  69. As a (very) trivial example, audio tracks can survive in most of the scenes of
  70. an application while meshes can be associated with a single scene and then
  71. discarded when users leave it.
  72. A cache offers a set of basic functionalities to query its internal state and to
  73. _organize_ it:
  74. ```cpp
  75. // gets the number of resources managed by a cache
  76. const auto size = cache.size();
  77. // checks if a cache contains at least a valid resource
  78. const auto empty = cache.empty();
  79. // clears a cache and discards its content
  80. cache.clear();
  81. ```
  82. Besides these member functions, a cache contains what is needed to load, use and
  83. discard resources of the given type.<br/>
  84. Before to explore this part of the interface, it makes sense to mention how
  85. resources are identified. The type of the identifiers to use is defined as:
  86. ```cpp
  87. entt::resource_cache<resource>::resource_type
  88. ```
  89. Where `resource_type` is an alias for `entt::hashed_string::hash_type`.
  90. Therefore, resource identifiers are created explicitly as in the following
  91. example:
  92. ```cpp
  93. constexpr auto identifier = entt::resource_cache<resource>::resource_type{"my/resource/identifier"_hs};
  94. // this is equivalent to the following
  95. constexpr auto hs = entt::hashed_string{"my/resource/identifier"};
  96. ```
  97. The class `hashed_string` is described in a dedicated section, so I won't go in
  98. details here.
  99. Resources are loaded and thus stored in a cache through the `load` member
  100. function. It accepts the loader to use as a template parameter, the resource
  101. identifier and the parameters used to construct the resource as arguments:
  102. ```cpp
  103. // uses the identifier declared above
  104. cache.load<my_loader>(identifier, 0);
  105. // uses a const char * directly as an identifier
  106. cache.load<my_loader>("another/identifier"_hs, 42);
  107. ```
  108. The function returns a handle to the resource, whether it already exists or is
  109. loaded. In case the loader returns an invalid pointer, the handle is invalid as
  110. well and therefore it can be easily used with an `if` statement:
  111. ```cpp
  112. if(auto handle = cache.load<my_loader>("another/identifier"_hs, 42); handle) {
  113. // ...
  114. }
  115. ```
  116. Before trying to load a resource, the `contains` member function can be used to
  117. know if a cache already contains a specific resource:
  118. ```cpp
  119. auto exists = cache.contains("my/identifier"_hs);
  120. ```
  121. There exists also a member function to use to force a reload of an already
  122. existing resource if needed:
  123. ```cpp
  124. auto handle = cache.reload<my_loader>("another/identifier"_hs, 42);
  125. ```
  126. As above, the function returns a handle to the resource that is invalid in case
  127. of errors. The `reload` member function is a kind of alias of the following
  128. snippet:
  129. ```cpp
  130. cache.discard(identifier);
  131. cache.load<my_loader>(identifier, 42);
  132. ```
  133. Where the `discard` member function is used to get rid of a resource if loaded.
  134. In case the cache doesn't contain a resource for the given identifier, `discard`
  135. does nothing and returns immediately.
  136. So far, so good. Resources are finally loaded and stored within the cache.<br/>
  137. They are returned to users in the form of handles. To get one of them later on:
  138. ```cpp
  139. auto handle = cache.handle("my/identifier"_hs);
  140. ```
  141. The idea behind a handle is the same of the flyweight pattern. In other terms,
  142. resources aren't copied around. Instead, instances are shared between handles.
  143. Users of a resource own a handle that guarantees that a resource isn't destroyed
  144. until all the handles are destroyed, even if the resource itself is removed from
  145. the cache.<br/>
  146. Handles are tiny objects both movable and copyable. They return the contained
  147. resource as a const reference on request:
  148. * By means of the `get` member function:
  149. ```cpp
  150. const auto &resource = handle.get();
  151. ```
  152. * Using the proper cast operator:
  153. ```cpp
  154. const auto &resource = handle;
  155. ```
  156. * Through the dereference operator:
  157. ```cpp
  158. const auto &resource = *handle;
  159. ```
  160. The resource can also be accessed directly using the arrow operator if required:
  161. ```cpp
  162. auto value = handle->value;
  163. ```
  164. To test if a handle is still valid, the cast operator to `bool` allows users to
  165. use it in a guard:
  166. ```cpp
  167. if(handle) {
  168. // ...
  169. }
  170. ```
  171. Finally, in case there is the need to load a resource and thus to get a handle
  172. without storing the resource itself in the cache, users can rely on the `temp`
  173. member function template.<br/>
  174. The declaration is similar to that of `load`, a (possibly invalid) handle for
  175. the resource is returned also in this case:
  176. ```cpp
  177. if(auto handle = cache.temp<my_loader>(42); handle) {
  178. // ...
  179. }
  180. ```
  181. Do not forget to test the handle for validity. Otherwise, getting a reference to
  182. the resource it points may result in undefined behavior.