Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Resource management

Michele Caini edited this pageMar 5, 2025 ·2 revisions

Crash Course: resource management

Table of Contents

Introduction

Resource management is usually one of the most critical parts of a game.Solutions are often tuned to the particular application. There exist severalapproaches and all of them are perfectly fine as long as they fit therequirements of the piece of software in which they are used.
Examples are loading everything on start, loading on request, predictiveloading, and so on.

EnTT does not pretend to offer aone-fits-all solution for the differentcases.
Instead, the library comes with a minimal, general purpose resource cache thatmight be useful in many cases.

The resource, the loader and the cache

Resource, loader and cache are the three main actors for the purpose.
Theresource is an image, an audio, a video or any other type:

structmy_resource {constint value; };

Theloader is a callable type, the aim of which is to load a specificresource:

structmy_loaderfinal {using result_type = std::shared_ptr<my_resource>;    result_typeoperator()(int value)const {// ...return std::make_shared<my_resource>(value);    }};

Its function operator can accept any arguments and should return a value of thedeclared result type (std::shared_ptr<my_resource> in the example).
A loader can also overload its function call operator to make it possible toconstruct the same or another resource from different lists of arguments.

Finally, a cache is a specialization of a class template tailored to a specificresource and (optionally) a loader:

using my_cache = entt::resource_cache<my_resource, my_loader>;// ...my_cache cache{};

The class is designed to create different caches for different resource typesand to manage each one independently in the most appropriate way.
As a (very) trivial example, audio tracks can survive in most of the scenes ofan application while meshes can be associated with a single scene only, thendiscarded when a player leaves it.

Resource handle

Resources are not returned directly to the caller. Instead, they are wrapped inaresource handle, an instance of theentt::resource class template.
For those who know theflyweight design pattern already, that is exactly whatit 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 defaultimplementation mostly maps the interface of its standard counterpart and onlyadds a few things on top of it.
However, the handle inEnTT is designed as a standalone class template. Thisis due to the fact that specializing a class in the standard library is oftenundefined behavior while having the ability to specialize the handle for one,more or all resource types could help over time.

Loaders

A loader is responsible forloading resources (quite obviously).
By default, it is just a callable object that forwards its arguments to theresource itself. That is, apassthrough type. All the work is demanded to theconstructor(s) of the resource itself.
Loaders also are fully customizable as expected.

A custom loader is a class with at least one function call operator and a membertype namedresult_type.
The loader is not required to return a resource handle. As long asreturn_typeis suitable for constructing a handle, that is fine.

When using the default handle, it expects a resource type which is convertibleto or suitable for constructing anstd::shared_ptr<Type> (whereType is theactual resource type).
In other terms, the loader should return shared pointers to the given resourcetype. However, this is not mandatory. Users can easily get around thisconstraint by specializing both the handle and the loader.

A cache forwards all its arguments to the loader if required. This means thatloaders can also support tag dispatching to offer different loading policies:

structmy_loader {using result_type = std::shared_ptr<my_resource>;structfrom_disk_tag{};structfrom_network_tag{};template<typename Args>    result_typeoperator()(from_disk_tag, Args&&... args) {// ...return std::make_shared<my_resource>(std::forward<Args>(args)...);    }template<typename Args>    result_typeoperator()(from_network_tag, Args&&... args) {// ...return std::make_shared<my_resource>(std::forward<Args>(args)...);    }}

This makes the whole loading logic quite flexible and easy to extend over time.

The cache class

The cache is the class that is asked toconnect the dots.
It loads the resources, stores them aside and returns handles as needed:

entt::resource_cache<my_resource, my_loader> cache{};

Under the hood, a cache is nothing more than a map where the key value has typeentt::id_type while the mapped value is whatever type its loader returns.
For this reason, it offers most of the functionalities a user would expect froma map, such asempty orsize and so on. Similarly, it is an iterable typethat also supports indexing by resource id:

for(auto [id, res]: cache) {// ...}if(entt::resource<my_resource> res = cache["resource/id"_hs]; res) {// ...}

Please, refer to the inline documentation for all the details about the otherfunctions (such ascontains orerase).

Set aside the part of the API that this classshares with a map, it also addssomething on top of it in order to address the most common requirements of aresource cache.
In particular, it does not have anemplace member function which is replacedbyload andforce_load instead (where the former loads a new resource onlyif not present while the second triggers a forced loading in any case):

auto ret = cache.load("resource/id"_hs);// true only if the resource was not already presentconstbool loaded = ret.second;// takes the resource handle pointed to by the returned iteratorentt::resource<my_resource> res = ret.first->second;

Note that the hashed string is used for convenience in the example above.
Resource identifiers are nothing more than integral values. Therefore, plainnumbers as well as non-class enum value are accepted.

It is worth mentioning that the iterators of a cache as well as its indexingoperators return resource handles rather than instances of the mapped type.
Since the cache has no control over the loader and a resource is not required toalso be convertible to bool, these handles can be invalid. This usually means anerror in the user logic, but it may also be anexpected event.
It is therefore recommended to verify handles validity with a check in debug(for example, when loading) or an appropriate logic in retail.

Try onlineDocumentationGitter chatDiscord channelDonate

Clone this wiki locally

[8]ページ先頭

©2009-2025 Movatter.jp