180 lines
14 KiB
HTML
180 lines
14 KiB
HTML
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
|
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US">
|
|
<head>
|
|
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
|
|
<meta http-equiv="X-UA-Compatible" content="IE=11"/>
|
|
<meta name="generator" content="Doxygen 1.9.7"/>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
<title>EnTT: Crash Course: resource management</title>
|
|
<link href="tabs.css" rel="stylesheet" type="text/css"/>
|
|
<script type="text/javascript" src="jquery.js"></script>
|
|
<script type="text/javascript" src="dynsections.js"></script>
|
|
<link href="search/search.css" rel="stylesheet" type="text/css"/>
|
|
<script type="text/javascript" src="search/searchdata.js"></script>
|
|
<script type="text/javascript" src="search/search.js"></script>
|
|
<link href="doxygen-awesome.css" rel="stylesheet" type="text/css" />
|
|
</head>
|
|
<body>
|
|
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
|
|
<div id="titlearea">
|
|
<table cellspacing="0" cellpadding="0">
|
|
<tbody>
|
|
<tr id="projectrow">
|
|
<td id="projectalign">
|
|
<div id="projectname">EnTT<span id="projectnumber"> 3.13.0</span>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<!-- end header part -->
|
|
<!-- Generated by Doxygen 1.9.7 -->
|
|
<script type="text/javascript">
|
|
/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */
|
|
var searchBox = new SearchBox("searchBox", "search/",'.html');
|
|
/* @license-end */
|
|
</script>
|
|
<script type="text/javascript" src="menudata.js"></script>
|
|
<script type="text/javascript" src="menu.js"></script>
|
|
<script type="text/javascript">
|
|
/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */
|
|
$(function() {
|
|
initMenu('',true,false,'search.php','Search');
|
|
$(document).ready(function() { init_search(); });
|
|
});
|
|
/* @license-end */
|
|
</script>
|
|
<div id="main-nav"></div>
|
|
<!-- window showing the filter options -->
|
|
<div id="MSearchSelectWindow"
|
|
onmouseover="return searchBox.OnSearchSelectShow()"
|
|
onmouseout="return searchBox.OnSearchSelectHide()"
|
|
onkeydown="return searchBox.OnSearchSelectKey(event)">
|
|
</div>
|
|
|
|
<!-- iframe showing the search results (closed by default) -->
|
|
<div id="MSearchResultsWindow">
|
|
<div id="MSearchResults">
|
|
<div class="SRPage">
|
|
<div id="SRIndex">
|
|
<div id="SRResults"></div>
|
|
<div class="SRStatus" id="Loading">Loading...</div>
|
|
<div class="SRStatus" id="Searching">Searching...</div>
|
|
<div class="SRStatus" id="NoMatches">No Matches</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div><!-- top -->
|
|
<div><div class="header">
|
|
<div class="headertitle"><div class="title">Crash Course: resource management</div></div>
|
|
</div><!--header-->
|
|
<div class="contents">
|
|
<div class="textblock"><p><a class="anchor" id="autotoc_md187"></a></p>
|
|
<h1><a class="anchor" id="autotoc_md188"></a>
|
|
Introduction</h1>
|
|
<p>Resource management is usually one of the most critical parts of a game. Solutions are often tuned to the particular application. There exist several approaches and all of them are perfectly fine as long as they fit the requirements of the piece of software in which they are used.<br />
|
|
Examples are loading everything on start, loading on request, predictive loading, and so on.</p>
|
|
<p><code>EnTT</code> doesn't pretend to offer a <em>one-fits-all</em> solution for the different cases.<br />
|
|
Instead, the library comes with a minimal, general purpose resource cache that might be useful in many cases.</p>
|
|
<h1><a class="anchor" id="autotoc_md189"></a>
|
|
The resource, the loader and the cache</h1>
|
|
<p>Resource, loader and cache are the three main actors for the purpose.<br />
|
|
The <em>resource</em> is an image, an audio, a video or any other type:</p>
|
|
<div class="fragment"><div class="line"><span class="keyword">struct </span>my_resource { <span class="keyword">const</span> <span class="keywordtype">int</span> value; };</div>
|
|
</div><!-- fragment --><p>The <em>loader</em> is a callable type the aim of which is to load a specific resource:</p>
|
|
<div class="fragment"><div class="line"><span class="keyword">struct </span>my_loader final {</div>
|
|
<div class="line"> <span class="keyword">using </span>result_type = std::shared_ptr<my_resource>;</div>
|
|
<div class="line"> </div>
|
|
<div class="line"> result_type operator()(<span class="keywordtype">int</span> value)<span class="keyword"> const </span>{</div>
|
|
<div class="line"> <span class="comment">// ...</span></div>
|
|
<div class="line"> <span class="keywordflow">return</span> std::make_shared<my_resource>(value);</div>
|
|
<div class="line"> }</div>
|
|
<div class="line">};</div>
|
|
</div><!-- fragment --><p>Its function operator can accept any arguments and should return a value of the declared result type (<code>std::shared_ptr<my_resource></code> 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.</p>
|
|
<p>Finally, a cache is a specialization of a class template tailored to a specific resource and (optionally) a loader:</p>
|
|
<div class="fragment"><div class="line"><span class="keyword">using </span>my_cache = <a class="code hl_class" href="classentt_1_1resource__cache.html">entt::resource_cache<my_resource, my_loader></a>;</div>
|
|
<div class="line"> </div>
|
|
<div class="line"><span class="comment">// ...</span></div>
|
|
<div class="line"> </div>
|
|
<div class="line">my_cache cache{};</div>
|
|
<div class="ttc" id="aclassentt_1_1resource__cache_html"><div class="ttname"><a href="classentt_1_1resource__cache.html">entt::resource_cache</a></div><div class="ttdoc">Basic cache for resources of any type.</div><div class="ttdef"><b>Definition</b> <a href="cache_8hpp_source.html#l00153">cache.hpp:153</a></div></div>
|
|
</div><!-- fragment --><p>The class is designed to create different caches for different resource types 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 only, then discarded when a player leaves it.</p>
|
|
<h2><a class="anchor" id="autotoc_md190"></a>
|
|
Resource handle</h2>
|
|
<p>Resources aren't returned directly to the caller. Instead, they are wrapped in a <em>resource handle</em>, an instance of the <code><a class="el" href="classentt_1_1resource.html" title="Basic resource handle.">entt::resource</a></code> class template.<br />
|
|
For those who know the <em>flyweight design pattern</em> already, that's exactly what it is. To all others, this is the time to brush up on some notions instead.</p>
|
|
<p>A shared pointer could have been used as a resource handle. In fact, the default implementation mostly maps the interface of its standard counterpart and only adds a few things on top of it.<br />
|
|
However, the handle in <code>EnTT</code> is designed as a standalone class template. This is due to the fact that specializing a class in the standard library is often undefined behavior while having the ability to specialize the handle for one, more or all resource types could help over time.</p>
|
|
<h2><a class="anchor" id="autotoc_md191"></a>
|
|
Loaders</h2>
|
|
<p>A loader is responsible for <em>loading</em> resources (quite obviously).<br />
|
|
By default, it's just a callable object that forwards its arguments to the resource itself. That is, a <em>passthrough type</em>. All the work is demanded to the constructor(s) of the resource itself.<br />
|
|
Loaders also are fully customizable as expected.</p>
|
|
<p>A custom loader is a class with at least one function call operator and a member type named <code>result_type</code>.<br />
|
|
The loader isn't required to return a resource handle. As long as <code>return_type</code> is suitable for constructing a handle, that's fine.</p>
|
|
<p>When using the default handle, it expects a resource type which is convertible to or suitable for constructing an <code>std::shared_ptr<Type></code> (where <code>Type</code> is the actual resource type).<br />
|
|
In other terms, the loader should return shared pointers to the given resource type. However, this isn't mandatory. Users can easily get around this constraint by specializing both the handle and the loader.</p>
|
|
<p>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:</p>
|
|
<div class="fragment"><div class="line"><span class="keyword">struct </span>my_loader {</div>
|
|
<div class="line"> <span class="keyword">using </span>result_type = std::shared_ptr<my_resource>;</div>
|
|
<div class="line"> </div>
|
|
<div class="line"> <span class="keyword">struct </span>from_disk_tag{};</div>
|
|
<div class="line"> <span class="keyword">struct </span>from_network_tag{};</div>
|
|
<div class="line"> </div>
|
|
<div class="line"> <span class="keyword">template</span><<span class="keyword">typename</span> Args></div>
|
|
<div class="line"> result_type operator()(from_disk_tag, Args&&... args) {</div>
|
|
<div class="line"> <span class="comment">// ...</span></div>
|
|
<div class="line"> <span class="keywordflow">return</span> std::make_shared<my_resource>(std::forward<Args>(args)...);</div>
|
|
<div class="line"> }</div>
|
|
<div class="line"> </div>
|
|
<div class="line"> <span class="keyword">template</span><<span class="keyword">typename</span> Args></div>
|
|
<div class="line"> result_type operator()(from_network_tag, Args&&... args) {</div>
|
|
<div class="line"> <span class="comment">// ...</span></div>
|
|
<div class="line"> <span class="keywordflow">return</span> std::make_shared<my_resource>(std::forward<Args>(args)...);</div>
|
|
<div class="line"> }</div>
|
|
<div class="line">}</div>
|
|
</div><!-- fragment --><p>This makes the whole loading logic quite flexible and easy to extend over time.</p>
|
|
<h2><a class="anchor" id="autotoc_md192"></a>
|
|
The cache class</h2>
|
|
<p>The cache is the class that is asked to <em>connect the dots</em>.<br />
|
|
It loads the resources, stores them aside and returns handles as needed:</p>
|
|
<div class="fragment"><div class="line"><a class="code hl_class" href="classentt_1_1resource__cache.html">entt::resource_cache<my_resource, my_loader></a> cache{};</div>
|
|
</div><!-- fragment --><p>Under the hood, a cache is nothing more than a map where the key value has type <code><a class="el" href="namespaceentt.html#a13e040e7b38a8f86d1ab2f096f37b627" title="Alias declaration for type identifiers.">entt::id_type</a></code> while the mapped value is whatever type its loader returns.<br />
|
|
For this reason, it offers most of the functionalities a user would expect from a map, such as <code>empty</code> or <code>size</code> and so on. Similarly, it's an iterable type that also supports indexing by resource id:</p>
|
|
<div class="fragment"><div class="line"><span class="keywordflow">for</span>(<span class="keyword">auto</span> [<span class="keywordtype">id</span>, res]: cache) {</div>
|
|
<div class="line"> <span class="comment">// ...</span></div>
|
|
<div class="line">}</div>
|
|
<div class="line"> </div>
|
|
<div class="line"><span class="keywordflow">if</span>(<a class="code hl_class" href="classentt_1_1resource.html">entt::resource<my_resource></a> res = cache[<span class="stringliteral">"resource/id"</span>_hs]; res) {</div>
|
|
<div class="line"> <span class="comment">// ...</span></div>
|
|
<div class="line">}</div>
|
|
<div class="ttc" id="aclassentt_1_1resource_html"><div class="ttname"><a href="classentt_1_1resource.html">entt::resource</a></div><div class="ttdoc">Basic resource handle.</div><div class="ttdef"><b>Definition</b> <a href="resource_8hpp_source.html#l00022">resource.hpp:22</a></div></div>
|
|
</div><!-- fragment --><p>Please, refer to the inline documentation for all the details about the other functions (such as <code>contains</code> or <code>erase</code>).</p>
|
|
<p>Set aside the part of the API that this class <em>shares</em> 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 <code>emplace</code> member function which is replaced by <code>load</code> and <code>force_load</code> instead (where the former loads a new resource only if not present while the second triggers a forced loading in any case):</p>
|
|
<div class="fragment"><div class="line"><span class="keyword">auto</span> ret = cache.load(<span class="stringliteral">"resource/id"</span>_hs);</div>
|
|
<div class="line"> </div>
|
|
<div class="line"><span class="comment">// true only if the resource was not already present</span></div>
|
|
<div class="line"><span class="keyword">const</span> <span class="keywordtype">bool</span> loaded = ret.second;</div>
|
|
<div class="line"> </div>
|
|
<div class="line"><span class="comment">// takes the resource handle pointed to by the returned iterator</span></div>
|
|
<div class="line"><a class="code hl_class" href="classentt_1_1resource.html">entt::resource<my_resource></a> res = ret.first->second;</div>
|
|
</div><!-- fragment --><p>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.</p>
|
|
<p>It's worth mentioning that the iterators of a cache as well as 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 <em>expected</em> 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. </p>
|
|
</div></div><!-- contents -->
|
|
</div><!-- PageDoc -->
|
|
<!-- start footer part -->
|
|
<hr class="footer"/><address class="footer"><small>
|
|
Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.7
|
|
</small></address>
|
|
</body>
|
|
</html>
|