فهرست منبع

resource: updated doc

Michele Caini 3 سال پیش
والد
کامیت
413630813a
1فایلهای تغییر یافته به همراه115 افزوده شده و 136 حذف شده
  1. 115 136
      docs/md/resource.md

+ 115 - 136
docs/md/resource.md

@@ -7,6 +7,9 @@
 
 * [Introduction](#introduction)
 * [The resource, the loader and the cache](#the-resource-the-loader-and-the-cache)
+  * [Resource handle](#resource-handle)
+  * [Loaders](#loader)
+  * [The cache class](#the-cache)
 <!--
 @endcond TURN_OFF_DOXYGEN
 -->
@@ -21,21 +24,14 @@ Examples are loading everything on start, loading on request, predictive
 loading, and so on.
 
 `EnTT` doesn't pretend to offer a _one-fits-all_ solution for the different
-cases. Instead, it offers a minimal and perhaps trivial cache that can be useful
-most of the time during prototyping and sometimes even in a production
-environments.<br/>
-For those interested in the subject, the plan is to improve it considerably over
-time in terms of performance, memory usage and functionalities. Hoping to make
-it, of course, one step at a time.
+cases.<br/>
+Instead, the library offers a minimal, general purpose resource cache that might
+be useful in many cases.
 
 # The resource, the loader and the cache
 
-There are three main actors in the model: the resource, the loader and the
-cache.
-
-The _resource_ is whatever users want it to be. An image, a video, an audio,
-whatever. There are no limits.<br/>
-As a minimal example:
+Resource, loader and cache are the three main actors for the purpose.<br/>
+The _resource_ is an image, an audio, a video or any other type:
 
 ```cpp
 struct my_resource { const int value; };
@@ -45,17 +41,22 @@ The _loader_ is a callable type the aim of which is to load a specific resource:
 
 ```cpp
 struct my_loader final {
-    entt::resource_handle<my_resource> operator()(int value) const {
+    using result_type = std::shared_ptr<my_resource>;
+
+    result_type operator()(int value) const {
         // ...
-        return std::shared_ptr<my_resource>(new my_resource{ value });
+        return std::make_shared<my_resource>(value);
     }
 };
 ```
 
-Its function operator can accept any argument and should return a resource
-handle to the expected type (`my_resource` in the example).<br/>
+Its function operator can accept any arguments and should return a value of the
+declared result type (`std::shared_ptr<my_resource>` in the example).<br/>
+A loader can also overload its function call operator to make it possible to
+construct the same or another resource from different lists of arguments.
+
 Finally, a cache is a specialization of a class template tailored to a specific
-resource and loader:
+resource and (optionally) a loader:
 
 ```cpp
 using my_cache = entt::resource_cache<my_resource, my_loader>;
@@ -65,149 +66,127 @@ using my_cache = entt::resource_cache<my_resource, my_loader>;
 my_cache cache{};
 ```
 
-The idea is to create different caches for different types of resources and to
-manage each one independently in the most appropriate way.<br/>
+The cache is meant to be used to create different caches for different types of
+resources and to manage each one independently in the most appropriate way.<br/>
 As a (very) trivial example, audio tracks can survive in most of the scenes of
-an application while meshes can be associated with a single scene and then
-discarded when users leave it.
-
-A cache offers a set of basic functionalities to query its internal state and to
-_organize_ it:
-
-```cpp
-// gets the number of resources managed by a cache
-const auto size = cache.size();
-
-// checks if a cache contains at least a valid resource
-const auto empty = cache.empty();
-
-// clears a cache and discards its content
-cache.clear();
-```
-
-Besides these member functions, a cache contains what is needed to load, use and
-discard resources of the given type.<br/>
-Before exploring this part of the interface, it makes sense to mention how
-resources are identified. They have type `id_type` and therefore they can be
-created explicitly as in the following example:
-
-```cpp
-constexpr auto identifier = "my/resource/identifier"_hs;
-// this is equivalent to the following
-constexpr entt::id_type hs = entt::hashed_string{"my/resource/identifier"};
-```
-
-The class `hashed_string` is described in a dedicated section, so I won't go in
-details here.
-
-Resources are loaded and thus stored in a cache through the `load` member
-function. It accepts the resource identifier and the parameters to use to create
-it:
+an application while meshes can be associated with a single scene only, then
+discarded when a player leaves it.
+
+## Resource handle
+
+Resources aren't returned directly to the caller. Instead, they are wrapped in a
+_resource handle_ identified by the `entt::resource` class template.<br/>
+For those who know the _flyweight design pattern_ already, that's exactly what
+it is. To all others, this is the time to brush up on some notions instead.
+
+A shared pointer could have been used as a resource handle. In fact, the default
+handle mostly maps the interface of its standard counterpart and only adds a few
+things to it.<br/>
+However, the handle in `EnTT` is designed as a standalone class template named
+`resource`. It boils down to the fact that specializing a class in the standard
+is often undefined behavior while having the ability to specialize the handle
+for one, more or all resource types could help over time.
+
+## Loaders
+
+A loader is a class that is responsible for _loading_ the resources.<br/>
+By default, it's just a callable object which forwards its arguments to the
+resource itself. That is, a _passthrough type_. All the work is demanded to the
+constructor(s) of the resource itself.<br/>
+Loaders also are fully customizable as expected.
+
+A custom loader is a class with at least one function call operator and a member
+type named `result_type`.<br/>
+The loader isn't required to return a resource handle. As long as `return_type`
+is suitable for constructing a handle, that's fine.
+
+When using the default handle, it expects a resource type which is convertible
+to or suitable for constructing an `std::shared_ptr<Type>` (where `Type` is the
+actual resource type).<br/>
+In other terms, the loader should return shared pointers to the given resource
+type. However, it isn't mandatory. Users can easily get around this constraint
+by specializing both the handle and the loader.
+
+A cache forwards all its arguments to the loader if required. This means that
+loaders can also support tag dispatching to offer different loading policies:
 
 ```cpp
-// uses the identifier declared above
-cache.load(identifier, 0);
+struct my_loader {
+    using result_type = std::shared_ptr<my_resource>;
 
-// uses a hashed string directly
-cache.load("another/identifier"_hs, 42);
-```
+    struct from_disk_tag{};
+    struct from_network_tag{};
 
-The function returns a resource handle, whether it already exists or is loaded.
-In case the loader returns an invalid pointer, the handle is invalid as well and
-therefore it can be easily used with an `if` statement:
+    template<typename Args>
+    result_type operator()(from_disk_tag, Args&&... args) {
+        // ...
+        return std::make_shared<my_resource>(std::forward<Args>(args)...);
+    }
 
-```cpp
-if(entt::resource_handle handle = cache.load("another/identifier"_hs, 42); handle) {
-    // ...
+    template<typename Args>
+    result_type operator()(from_network_tag, Args&&... args) {
+        // ...
+        return std::make_shared<my_resource>(std::forward<Args>(args)...);
+    }
 }
 ```
 
-Before trying to load a resource, the `contains` member function can be used to
-know if a cache already contains a specific resource:
+This makes the whole loading logic quite flexible and easy to extend over time.
 
-```cpp
-auto exists = cache.contains("my/identifier"_hs);
-```
+## The cache class
 
-There exists also a member function to use to force a reload of an already
-existing resource if needed:
+The cache is the class that is asked to _connect the dots_.<br/>
+It loads the resources, store them aside and returns handles as needed:
 
 ```cpp
-auto handle = cache.reload("another/identifier"_hs, 42);
+entt::resource_cache<my_resource, my_loader> cache{};
 ```
 
-As above, the function returns a resource handle that is invalid in case of
-errors. The `reload` member function is a kind of alias of the following
-snippet:
+Under the hood, a cache is nothing more than a map where the key value has type
+`entt::id_type` while the mapped value is whatever type its loader returns.<br/>
+For this reason, it offers most of the functionality a user would expect from a
+map, such as `empty` or `size` and so on. Similarly, it's an iterable type that
+also supports indexing by resource id:
 
 ```cpp
-cache.discard(identifier);
-cache.load(identifier, 42);
-```
-
-Where the `discard` member function is used to get rid of a resource if loaded.
-In case the cache doesn't contain a resource for the given identifier, `discard`
-does nothing and returns immediately.
-
-So far, so good. Resources are finally loaded and stored within the cache.<br/>
-They are returned to users in the form of handles. To get one of them later on:
+for(entt::resource<my_resource> curr: cache) {
+    // ...
+}
 
-```cpp
-auto handle = cache.handle("my/identifier"_hs);
+if(entt::resource<my_resource> res = cache["resource/id"_hs]; res) {
+    // ...
+}
 ```
 
-The idea behind a handle is the same of the flyweight pattern. In other terms,
-resources aren't copied around. Instead, instances are shared between handles.
-Users of a resource own a handle that guarantees that a resource isn't destroyed
-until all the handles are destroyed, even if the resource itself is removed from
-the cache.<br/>
-Handles are tiny objects both movable and copyable. They return the contained
-resource as a (possibly const) reference on request:
-
-* By means of the `get` member function:
-
-  ```cpp
-  auto &resource = handle.get();
-  ```
-
-* Using the proper cast operator:
-
-  ```cpp
-  auto &resource = handle;
-  ```
+Please, refer to the inline documentation for all the details about the other
+functions (for example `contains` or `erase`).
 
-* Through the dereference operator:
-
-  ```cpp
-  auto &resource = *handle;
-  ```
-
-The resource can also be accessed directly using the arrow operator if required:
-
-```cpp
-auto value = handle->value;
-```
-
-To test if a handle is still valid, the cast operator to `bool` allows users to
-use it in a guard:
+Set aside the part of the API that this class shares with a map, it also adds
+something on top of it in order to address the most common requirements of a
+resource cache.<br/>
+In particular, it doesn't have an `emplace` member function which is replaced by
+`load` and `force_load` instead (where the former loads a new resource only if
+not present while the second triggers a forced loading in any case):
 
 ```cpp
-if(handle) {
-    // ...
-}
-```
+auto ret = cache.load("resource/id"_hs);
 
-Finally, in case there is the need to load a resource and thus to get a handle
-without storing the resource itself in the cache, users can rely on the `temp`
-member function template.<br/>
-The declaration is similar to that of `load`, a (possibly invalid) handle for
-the resource is returned also in this case:
+// true only if the resource was not already present
+const bool loaded = ret.second;
 
-```cpp
-if(auto handle = cache.temp(42); handle) {
-    // ...
-}
+// takes the resource handle pointed to by the returned iterator
+entt::resource<my_resource> res = *ret.first;
 ```
 
-Do not forget to test the handle for validity. Otherwise, getting a reference to
-the resource it points may result in undefined behavior.
+Note that the hashed string is used for convenience in the example above.<br/>
+Resource identifiers are nothing more than integral values. Therefore, plain
+numbers as well as non-class enum value are accepted.
+
+Moreover, it's worth mentioning that both the iterators of a cache and its
+indexing operators return resource handles rather than instances of the mapped
+type.<br/>
+Since the cache has no control over the loader and a resource isn't required to
+also be convertible to bool, these handles can be invalid. This usually means an
+error in the user logic but it may also be an _expected_ event.<br/>
+It's therefore recommended to verify handles validity with a check in debug (for
+example, when loading) or an appropriate logic in retail.