review: documentation
This commit is contained in:
@@ -218,6 +218,4 @@ add_custom_target(
|
||||
README.md
|
||||
TODO
|
||||
.travis.yml
|
||||
docs/CONTRIBUTING.md
|
||||
docs/extra.dox
|
||||
)
|
||||
|
||||
@@ -20,3 +20,17 @@ install(
|
||||
DIRECTORY ${DOXY_OUTPUT_DIRECTORY}/html
|
||||
DESTINATION share/${PROJECT_NAME}-${PROJECT_VERSION}/
|
||||
)
|
||||
|
||||
add_custom_target(
|
||||
docs_aob
|
||||
SOURCES
|
||||
CONTRIBUTING.md
|
||||
core.md
|
||||
entity.md
|
||||
locator.md
|
||||
process.md
|
||||
resource.md
|
||||
shared.md
|
||||
signal.md
|
||||
extra.dox
|
||||
)
|
||||
|
||||
167
docs/core.md
Normal file
167
docs/core.md
Normal file
@@ -0,0 +1,167 @@
|
||||
# Crash Course: core functionalities
|
||||
|
||||
<!--
|
||||
@cond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
# Table of Contents
|
||||
|
||||
* [Introduction](#introduction)
|
||||
* [Compile-time identifiers](#compile-time-identifiers)
|
||||
* [Runtime identifiers](#runtime-identifiers)
|
||||
* [Hashed strings](#hashed-strings)
|
||||
* [Conflicts](#conflicts)
|
||||
* [Monostate](#monostate)
|
||||
<!--
|
||||
@endcond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
|
||||
# Introduction
|
||||
|
||||
`EnTT` comes with a bunch of core functionalities mostly used by the other parts
|
||||
of the library itself.<br/>
|
||||
Hardly users will include these features in their code, but it's worth
|
||||
describing what `EnTT` offers so as not to reinvent the wheel in case of need.
|
||||
|
||||
# Compile-time identifiers
|
||||
|
||||
Sometimes it's useful to be able to give unique identifiers to types at
|
||||
compile-time.<br/>
|
||||
There are plenty of different solutions out there and I could have used one of
|
||||
them. However, I decided to spend my time to define a compact and versatile tool
|
||||
that fully embraces what the modern C++ has to offer.
|
||||
|
||||
The _result of my efforts_ is the `Identifier` class template:
|
||||
|
||||
```cpp
|
||||
#include <ident.hpp>
|
||||
|
||||
// defines the identifiers for the given types
|
||||
using ID = entt::Identifier<AType, AnotherType>;
|
||||
|
||||
// ...
|
||||
|
||||
switch(aTypeIdentifier) {
|
||||
case ID::get<AType>():
|
||||
// ...
|
||||
break;
|
||||
case ID::get<AnotherType>():
|
||||
// ...
|
||||
break;
|
||||
default:
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
This is all what the class template has to offer: a static `get` member function
|
||||
that returns a numerical identifier for the given type. It can be used in any
|
||||
context where constant expressions are required.
|
||||
|
||||
As long as the list remains unchanged, identifiers are also guaranteed to be the
|
||||
same for every run. In case they have been used in a production environment and
|
||||
a type has to be removed, one can just use a placeholder to left the other
|
||||
identifiers unchanged:
|
||||
|
||||
```cpp
|
||||
template<typename> struct IgnoreType {};
|
||||
|
||||
using ID = entt::Identifier<
|
||||
ATypeStillValid,
|
||||
IgnoreType<ATypeNoLongerValid>,
|
||||
AnotherTypeStillValid
|
||||
>;
|
||||
```
|
||||
|
||||
A bit ugly to see, but it works at least.
|
||||
|
||||
# Runtime identifiers
|
||||
|
||||
Sometimes it's useful to be able to give unique identifiers to types at
|
||||
runtime.<br/>
|
||||
There are plenty of different solutions out there and I could have used one of
|
||||
them. In fact, I adapted the most common one to my requirements and used it
|
||||
extensively within the entire library.
|
||||
|
||||
It's the `Family` class. Here is an example of use directly from the
|
||||
entity-component system:
|
||||
|
||||
```cpp
|
||||
using component_family = entt::Family<struct InternalRegistryComponentFamily>;
|
||||
|
||||
// ...
|
||||
|
||||
template<typename Component>
|
||||
component_type component() const noexcept {
|
||||
return component_family::type<Component>();
|
||||
}
|
||||
```
|
||||
|
||||
This is all what a _family_ has to offer: a `type` member function that returns
|
||||
a numerical identifier for the given type.
|
||||
|
||||
Please, note that identifiers aren't guaranteed to be the same for every run.
|
||||
Indeed it mostly depends on the flow of execution.
|
||||
|
||||
# Hashed strings
|
||||
|
||||
A hashed string is a zero overhead resource identifier. Users can use
|
||||
human-readable identifiers in the codebase while using their numeric
|
||||
counterparts at runtime, thus without affecting performance.<br/>
|
||||
The class has an implicit `constexpr` constructor that chews a bunch of
|
||||
characters. Once created, all what one can do with it is getting back the
|
||||
original string or converting it into a number.<br/>
|
||||
The good part is that a hashed string can be used wherever a constant expression
|
||||
is required and no _string-to-number_ conversion will take place at runtime if
|
||||
used carefully.
|
||||
|
||||
Example of use:
|
||||
|
||||
```cpp
|
||||
auto load(entt::HashedString::hash_type resource) {
|
||||
// uses the numeric representation of the resource to load and return it
|
||||
}
|
||||
|
||||
auto resource = load(entt::HashedString{"gui/background"});
|
||||
```
|
||||
|
||||
There is also a _user defined literal_ dedicated to hashed strings to make them
|
||||
more user-friendly:
|
||||
|
||||
```cpp
|
||||
constexpr auto str = "text"_hs;
|
||||
```
|
||||
|
||||
## Conflicts
|
||||
|
||||
The hashed string class uses internally FNV-1a to compute the numeric
|
||||
counterpart of a string. Because of the _pigeonhole principle_, conflicts are
|
||||
possible. This is a fact.<br/>
|
||||
There is no silver bullet to solve the problem of conflicts when dealing with
|
||||
hashing functions. In this case, the best solution seemed to be to give up.
|
||||
That's all.<br/>
|
||||
After all, human-readable resource identifiers aren't something strictly defined
|
||||
and over which users have not the control. Choosing a slightly different
|
||||
identifier is probably the best solution to make the conflict disappear in this
|
||||
case.
|
||||
|
||||
# Monostate
|
||||
|
||||
The monostate pattern is often presented as an alternative to a singleton based
|
||||
configuration system. This is exactly its purpose in `EnTT`. Moreover, this
|
||||
implementation is thread safe by design (hopefully).<br/>
|
||||
Keys are represented by hashed strings, values are basic types like `int`s or
|
||||
`bool`s. Values of different types can be associated to each key, even more than
|
||||
one at a time. Because of this, users must pay attention to use the same type
|
||||
both during an assignment and when they try to read back their data. Otherwise,
|
||||
they will probably incur in unexpected results.
|
||||
|
||||
Example of use:
|
||||
|
||||
```cpp
|
||||
entt::Monostate<entt::HashedString{"mykey"}>{} = true;
|
||||
entt::Monostate<"mykey"_hs>{} = 42;
|
||||
|
||||
// ...
|
||||
|
||||
const bool b = entt::Monostate<"mykey"_hs>{};
|
||||
const int i = entt::Monostate<entt::HashedString{"mykey"}>{};
|
||||
```
|
||||
1400
docs/entity.md
Normal file
1400
docs/entity.md
Normal file
File diff suppressed because it is too large
Load Diff
75
docs/locator.md
Normal file
75
docs/locator.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# Crash Course: service locator
|
||||
|
||||
<!--
|
||||
@cond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
# Table of Contents
|
||||
|
||||
* [Introduction](#introduction)
|
||||
* [Service locator](#service-locator)
|
||||
<!--
|
||||
@endcond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
|
||||
# Introduction
|
||||
|
||||
Usually service locators are tightly bound to the services they expose and it's
|
||||
hard to define a general purpose solution. This template based implementation
|
||||
tries to fill the gap and to get rid of the burden of defining a different
|
||||
specific locator for each application.<br/>
|
||||
This class is tiny, partially unsafe and thus risky to use. Moreover it doesn't
|
||||
fit probably most of the scenarios in which a service locator is required. Look
|
||||
at it as a small tool that can sometimes be useful if users know how to handle
|
||||
it.
|
||||
|
||||
# Service locator
|
||||
|
||||
The API is straightforward. The basic idea is that services are implemented by
|
||||
means of interfaces and rely on polymorphism.<br/>
|
||||
The locator is instantiated with the base type of the service if any and a
|
||||
concrete implementation is provided along with all the parameters required to
|
||||
initialize it. As an example:
|
||||
|
||||
```cpp
|
||||
// the service has no base type, a locator is used to treat it as a kind of singleton
|
||||
entt::ServiceLocator<MyService>::set(params...);
|
||||
|
||||
// sets up an opaque service
|
||||
entt::ServiceLocator<AudioInterface>::set<AudioImplementation>(params...);
|
||||
|
||||
// resets (destroys) the service
|
||||
entt::ServiceLocator<AudioInterface>::reset();
|
||||
```
|
||||
|
||||
The locator can also be queried to know if an active service is currently set
|
||||
and to retrieve it if necessary (either as a pointer or as a reference):
|
||||
|
||||
```cpp
|
||||
// no service currently set
|
||||
auto empty = entt::ServiceLocator<AudioInterface>::empty();
|
||||
|
||||
// gets a (possibly empty) shared pointer to the service ...
|
||||
std::shared_ptr<AudioInterface> ptr = entt::ServiceLocator<AudioInterface>::get();
|
||||
|
||||
// ... or a reference, but it's undefined behaviour if the service isn't set yet
|
||||
AudioInterface &ref = entt::ServiceLocator<AudioInterface>::ref();
|
||||
```
|
||||
|
||||
A common use is to wrap the different locators in a container class, creating
|
||||
aliases for the various services:
|
||||
|
||||
```cpp
|
||||
struct Locator {
|
||||
using Camera = entt::ServiceLocator<CameraInterface>;
|
||||
using Audio = entt::ServiceLocator<AudioInterface>;
|
||||
// ...
|
||||
};
|
||||
|
||||
// ...
|
||||
|
||||
void init() {
|
||||
Locator::Camera::set<CameraNull>();
|
||||
Locator::Audio::set<AudioImplementation>(params...);
|
||||
// ...
|
||||
}
|
||||
```
|
||||
211
docs/process.md
Normal file
211
docs/process.md
Normal file
@@ -0,0 +1,211 @@
|
||||
# Crash Course: cooperative scheduler
|
||||
|
||||
<!--
|
||||
@cond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
# Table of Contents
|
||||
|
||||
* [Introduction](#introduction)
|
||||
* [The process](#the-process)
|
||||
* [Adaptor](#adaptor)
|
||||
* [The scheduler](#the-scheduler)
|
||||
<!--
|
||||
@endcond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
|
||||
# Introduction
|
||||
|
||||
Sometimes processes are a useful tool to work around the strict definition of a
|
||||
system and introduce logic in a different way, usually without resorting to the
|
||||
introduction of other components.
|
||||
|
||||
`EnTT` offers a minimal support to this paradigm by introducing a few classes
|
||||
that users can use to define and execute cooperative processes.
|
||||
|
||||
# The process
|
||||
|
||||
A typical process must inherit from the `Process` class template that stays true
|
||||
to the CRTP idiom. Moreover, derived classes must specify what's the intended
|
||||
type for elapsed times.
|
||||
|
||||
A process should expose publicly the following member functions whether
|
||||
required (note that it isn't required to define a function unless the derived
|
||||
class wants to _override_ the default behavior):
|
||||
|
||||
* `void update(Delta, void *);`
|
||||
|
||||
It's invoked once per tick until a process is explicitly aborted or it
|
||||
terminates either with or without errors. Even though it's not mandatory to
|
||||
declare this member function, as a rule of thumb each process should at
|
||||
least define it to work properly. The `void *` parameter is an opaque pointer
|
||||
to user data (if any) forwarded directly to the process during an update.
|
||||
|
||||
* `void init(void *);`
|
||||
|
||||
It's invoked at the first tick, immediately before an update. The `void *`
|
||||
parameter is an opaque pointer to user data (if any) forwarded directly to the
|
||||
process during an update.
|
||||
|
||||
* `void succeeded();`
|
||||
|
||||
It's invoked in case of success, immediately after an update and during the
|
||||
same tick.
|
||||
|
||||
* `void failed();`
|
||||
|
||||
It's invoked in case of errors, immediately after an update and during the
|
||||
same tick.
|
||||
|
||||
* `void aborted();`
|
||||
|
||||
It's invoked only if a process is explicitly aborted. There is no guarantee
|
||||
that it executes in the same tick, this depends solely on whether the
|
||||
process is aborted immediately or not.
|
||||
|
||||
Derived classes can also change the internal state of a process by invoking
|
||||
`succeed` and `fail`, as well as `pause` and `unpause` the process itself. All
|
||||
these are protected member functions made available to be able to manage the
|
||||
life cycle of a process from a derived class.
|
||||
|
||||
Here is a minimal example for the sake of curiosity:
|
||||
|
||||
```cpp
|
||||
struct MyProcess: entt::Process<MyProcess, std::uint32_t> {
|
||||
using delta_type = std::uint32_t;
|
||||
|
||||
void update(delta_type delta, void *) {
|
||||
remaining = delta > remaining ? delta_type{] : (remaining - delta);
|
||||
|
||||
// ...
|
||||
|
||||
if(!remaining) {
|
||||
succeed();
|
||||
}
|
||||
}
|
||||
|
||||
void init(void *data) {
|
||||
remaining = *static_cast<delta_type *>(data);
|
||||
}
|
||||
|
||||
private:
|
||||
delta_type remaining;
|
||||
};
|
||||
```
|
||||
|
||||
## Adaptor
|
||||
|
||||
Lambdas and functors can't be used directly with a scheduler for they are not
|
||||
properly defined processes with managed life cycles.<br/>
|
||||
This class helps in filling the gap and turning lambdas and functors into
|
||||
full featured processes usable by a scheduler.
|
||||
|
||||
The function call operator has a signature similar to the one of the `update`
|
||||
function of a process but for the fact that it receives two extra arguments to
|
||||
call whenever a process is terminated with success or with an error:
|
||||
|
||||
```cpp
|
||||
void(Delta delta, void *data, auto succeed, auto fail);
|
||||
```
|
||||
|
||||
Parameters have the following meaning:
|
||||
|
||||
* `delta` is the elapsed time.
|
||||
* `data` is an opaque pointer to user data if any, `nullptr` otherwise.
|
||||
* `succeed` is a function to call when a process terminates with success.
|
||||
* `fail` is a function to call when a process terminates with errors.
|
||||
|
||||
Both `succeed` and `fail` accept no parameters at all.
|
||||
|
||||
Note that usually users shouldn't worry about creating adaptors at all. A
|
||||
scheduler creates them internally each and every time a lambda or a functor is
|
||||
used as a process.
|
||||
|
||||
# The scheduler
|
||||
|
||||
A cooperative scheduler runs different processes and helps managing their life
|
||||
cycles.
|
||||
|
||||
Each process is invoked once per tick. If it terminates, it's removed
|
||||
automatically from the scheduler and it's never invoked again. Otherwise it's
|
||||
a good candidate to run once more the next tick.<br/>
|
||||
A process can also have a child. In this case, the process is replaced with
|
||||
its child when it terminates if it returns with success. In case of errors,
|
||||
both the process and its child are discarded. This way, it's easy to create
|
||||
chain of processes to run sequentially.
|
||||
|
||||
Using a scheduler is straightforward. To create it, users must provide only the
|
||||
type for the elapsed times and no arguments at all:
|
||||
|
||||
```cpp
|
||||
Scheduler<std::uint32_t> scheduler;
|
||||
```
|
||||
|
||||
It has member functions to query its internal data structures, like `empty` or
|
||||
`size`, as well as a `clear` utility to reset it to a clean state:
|
||||
|
||||
```cpp
|
||||
// checks if there are processes still running
|
||||
const auto empty = scheduler.empty();
|
||||
|
||||
// gets the number of processes still running
|
||||
Scheduler<std::uint32_t>::size_type size = scheduler.size();
|
||||
|
||||
// resets the scheduler to its initial state and discards all the processes
|
||||
scheduler.clear();
|
||||
```
|
||||
|
||||
To attach a process to a scheduler there are mainly two ways:
|
||||
|
||||
* If the process inherits from the `Process` class template, it's enough to
|
||||
indicate its type and submit all the parameters required to construct it to
|
||||
the `attach` member function:
|
||||
|
||||
```cpp
|
||||
scheduler.attach<MyProcess>("foobar");
|
||||
```
|
||||
|
||||
* Otherwise, in case of a lambda or a functor, it's enough to provide an
|
||||
instance of the class to the `attach` member function:
|
||||
|
||||
```cpp
|
||||
scheduler.attach([](auto...){ /* ... */ });
|
||||
```
|
||||
|
||||
In both cases, the return value is an opaque object that offers a `then` member
|
||||
function to use to create chains of processes to run sequentially.<br/>
|
||||
As a minimal example of use:
|
||||
|
||||
```cpp
|
||||
// schedules a task in the form of a lambda function
|
||||
scheduler.attach([](auto delta, void *, auto succeed, auto fail) {
|
||||
// ...
|
||||
})
|
||||
// appends a child in the form of another lambda function
|
||||
.then([](auto delta, void *, auto succeed, auto fail) {
|
||||
// ...
|
||||
})
|
||||
// appends a child in the form of a process class
|
||||
.then<MyProcess>();
|
||||
```
|
||||
|
||||
To update a scheduler and thus all its processes, the `update` member function
|
||||
is the way to go:
|
||||
|
||||
```cpp
|
||||
// updates all the processes, no user data are provided
|
||||
scheduler.update(delta);
|
||||
|
||||
// updates all the processes and provides them with custom data
|
||||
scheduler.update(delta, &data);
|
||||
```
|
||||
|
||||
In addition to these functions, the scheduler offers an `abort` member function
|
||||
that can be used to discard all the running processes at once:
|
||||
|
||||
```cpp
|
||||
// aborts all the processes abruptly ...
|
||||
scheduler.abort(true);
|
||||
|
||||
// ... or gracefully during the next tick
|
||||
scheduler.abort();
|
||||
```
|
||||
240
docs/resource.md
Normal file
240
docs/resource.md
Normal file
@@ -0,0 +1,240 @@
|
||||
# Crash Course: resource management
|
||||
|
||||
<!--
|
||||
@cond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
# Table of Contents
|
||||
|
||||
* [Introduction](#introduction)
|
||||
* [The resource, the loader and the cache](#the-resource-the-loader-and-the-cache)
|
||||
<!--
|
||||
@endcond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
|
||||
# Introduction
|
||||
|
||||
Resource management is usually one of the most critical part of a software like
|
||||
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.
|
||||
|
||||
`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
|
||||
environment.<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.
|
||||
|
||||
# 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:
|
||||
|
||||
```cpp
|
||||
struct MyResource { const int value; };
|
||||
```
|
||||
|
||||
A _loader_ is a class the aim of which is to load a specific resource. It has to
|
||||
inherit directly from the dedicated base class as in the following example:
|
||||
|
||||
```cpp
|
||||
struct MyLoader final: entt::ResourceLoader<MyLoader, MyResource> {
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
Where `MyResource` is the type of resources it creates.<br/>
|
||||
A resource loader must also expose a public const member function named `load`
|
||||
that accepts a variable number of arguments and returns a shared pointer to a
|
||||
resource.<br/>
|
||||
As an example:
|
||||
|
||||
```cpp
|
||||
struct MyLoader: entt::ResourceLoader<MyLoader, MyResource> {
|
||||
std::shared_ptr<MyResource> load(int value) const {
|
||||
// ...
|
||||
return std::shared_ptr<MyResource>(new MyResource{ value });
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
In general, resource loaders should not have a state or retain data of any type.
|
||||
They should let the cache manage their resources instead.<br/>
|
||||
As a side note, base class and CRTP idiom aren't strictly required with the
|
||||
current implementation. One could argue that a cache can easily work with
|
||||
loaders of any type. However, future changes won't be breaking ones by forcing
|
||||
the use of a base class today and that's why the model is already in its place.
|
||||
|
||||
Finally, a cache is a specialization of a class template tailored to a specific
|
||||
resource:
|
||||
|
||||
```cpp
|
||||
using MyResourceCache = entt::ResourceCache<MyResource>;
|
||||
|
||||
// ...
|
||||
|
||||
MyResourceCache cache{};
|
||||
```
|
||||
|
||||
The idea is to create different caches for different types of resources and to
|
||||
manage each one independently and 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, it contains what is needed to load, use and
|
||||
discard resources of the given type.<br/>
|
||||
Before to explore this part of the interface, it makes sense to mention how
|
||||
resources are identified. The type of the identifiers to use is defined as:
|
||||
|
||||
```cpp
|
||||
entt::ResourceCache<Resource>::resource_type
|
||||
```
|
||||
|
||||
Where `resource_type` is an alias for `entt::HashedString`. Therefore, resource
|
||||
identifiers are created explicitly as in the following example:
|
||||
|
||||
```cpp
|
||||
constexpr auto identifier = entt::ResourceCache<Resource>::resource_type{"my/resource/identifier"};
|
||||
// this is equivalent to the following
|
||||
constexpr auto hs = entt::HashedString{"my/resource/identifier"};
|
||||
```
|
||||
|
||||
The class `HashedString` is described in a dedicated section, so I won't do in
|
||||
details here.
|
||||
|
||||
Resources are loaded and thus stored in a cache through the `load` member
|
||||
function. It accepts the loader to use as a template parameter, the resource
|
||||
identifier and the parameters used to construct the resource as arguments:
|
||||
|
||||
```cpp
|
||||
// uses the identifier declared above
|
||||
cache.load<MyLoader>(identifier, 0);
|
||||
|
||||
// uses a const char * directly as an identifier
|
||||
cache.load<MyLoader>("another/identifier", 42);
|
||||
```
|
||||
|
||||
The return value can be used to know if the resource has been loaded correctly.
|
||||
In case the loader returns an invalid pointer or the resource already exists in
|
||||
the cache, a false value is returned:
|
||||
|
||||
```cpp
|
||||
if(!cache.load<MyLoader>("another/identifier", 42)) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Unfortunately, in this case there is no way to know what was the problem
|
||||
exactly. However, before trying to load a resource or after an error, one can
|
||||
use the `contains` member function to know if a cache already contains a
|
||||
specific resource:
|
||||
|
||||
```cpp
|
||||
auto exists = cache.contains("my/identifier");
|
||||
```
|
||||
|
||||
There exists also a member function to use to force a reload of an already
|
||||
existing resource if needed:
|
||||
|
||||
```cpp
|
||||
auto result = cache.reload<MyLoader>("another/identifier", 42);
|
||||
```
|
||||
|
||||
As above, the function returns true in case of success, false otherwise. The
|
||||
sole difference in this case is that an error necessarily means that the loader
|
||||
has failed for some reasons to load the resource.<br/>
|
||||
Note that the `reload` member function is a kind of alias of the following
|
||||
snippet:
|
||||
|
||||
```cpp
|
||||
cache.discard(identifier);
|
||||
cache.load<MyLoader>(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, the
|
||||
function 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:
|
||||
|
||||
```cpp
|
||||
auto handle = cache.handle("my/identifier");
|
||||
```
|
||||
|
||||
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 owns a handle and it 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 returns the contained
|
||||
resource as a const reference on request:
|
||||
|
||||
* By means of the `get` member function:
|
||||
|
||||
```cpp
|
||||
const auto &resource = handle.get();
|
||||
```
|
||||
|
||||
* Using the proper cast operator:
|
||||
|
||||
```cpp
|
||||
const auto &resource = handle;
|
||||
```
|
||||
|
||||
* Through the dereference operator:
|
||||
|
||||
```cpp
|
||||
const 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:
|
||||
|
||||
```cpp
|
||||
if(handle) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
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 the one of `load` but for the fact that it doesn't
|
||||
return a boolean value. Instead, it returns a (possibly invalid) handle for the
|
||||
resource:
|
||||
|
||||
```cpp
|
||||
auto handle = cache.temp<MyLoader>("another/identifier", 42);
|
||||
```
|
||||
|
||||
Do not forget to test the handle for validity. Otherwise, getting the reference
|
||||
to the resource it points may result in undefined behavior.
|
||||
39
docs/shared.md
Normal file
39
docs/shared.md
Normal file
@@ -0,0 +1,39 @@
|
||||
### EnTT and shared libraries
|
||||
|
||||
To make sure that an application and a shared library that use both `EnTT` can
|
||||
interact correctly when symbols are hidden by default, there are some tricks to
|
||||
follow.<br/>
|
||||
In particular and in order to avoid undefined behaviors, all the instantiation
|
||||
of the `Family` class template shall be made explicit along with the system-wide
|
||||
specifier to use to export them.
|
||||
|
||||
At the time I'm writing this document, the classes that use internally the above
|
||||
mentioned class template are `Dispatcher`, `Emitter` and `Registry`. Therefore
|
||||
and as an example, if you use the `Registry` class template in your shared
|
||||
library and want to set symbols visibility to _hidden_ by default, the following
|
||||
lines are required to allow it to function properly with a client that also uses
|
||||
the `Registry` somehow:
|
||||
|
||||
* On GNU/Linux:
|
||||
|
||||
```cpp
|
||||
namespace entt {
|
||||
template class __attribute__((visibility("default"))) Family<struct InternalRegistryTagFamily>;
|
||||
template class __attribute__((visibility("default"))) Family<struct InternalRegistryComponentFamily>;
|
||||
template class __attribute__((visibility("default"))) Family<struct InternalRegistryHandlerFamily>;
|
||||
}
|
||||
```
|
||||
|
||||
* On Windows:
|
||||
|
||||
```cpp
|
||||
namespace entt {
|
||||
template class __declspec(dllexport) Family<struct InternalRegistryTagFamily>;
|
||||
template class __declspec(dllexport) Family<struct InternalRegistryComponentFamily>;
|
||||
template class __declspec(dllexport) Family<struct InternalRegistryHandlerFamily>;
|
||||
}
|
||||
```
|
||||
|
||||
Otherwise, the risk is that type identifiers are different between the shared
|
||||
library and the application and this will prevent the whole thing from
|
||||
functioning correctly for obvious reasons.
|
||||
412
docs/signal.md
Normal file
412
docs/signal.md
Normal file
@@ -0,0 +1,412 @@
|
||||
# Crash Course: events, signals and everything in between
|
||||
|
||||
<!--
|
||||
@cond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
# Table of Contents
|
||||
|
||||
* [Introduction](#introduction)
|
||||
* [Signals](#signals)
|
||||
* [Delegate](#delegate)
|
||||
* [Event dispatcher](#event-dispatcher)
|
||||
* [Event emitter](#event-emitter)
|
||||
<!--
|
||||
@endcond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
|
||||
# Introduction
|
||||
|
||||
Signals are usually a core part of games and software architectures in
|
||||
general.<br/>
|
||||
Roughly speaking, they help to decouple the various parts of a system while
|
||||
allowing them to communicate with each other somehow.
|
||||
|
||||
The so called _modern C++_ comes with a tool that can be useful in these terms,
|
||||
the `std::function`. As an example, it can be used to create delegates.<br/>
|
||||
However, there is no guarantee that an `std::function` does not perform
|
||||
allocations under the hood and this could be problematic sometimes. Furthermore,
|
||||
it solves a problem but may not adapt well to other requirements that may arise
|
||||
from time to time.
|
||||
|
||||
In case that the flexibility and potential of an `std::function` are not
|
||||
required or where you are looking for something different, `EnTT` offers a full
|
||||
set of classes to solve completely different problems.
|
||||
|
||||
# Signals
|
||||
|
||||
Signal handlers work with naked pointers, function pointers and pointers to
|
||||
member functions. Listeners can be any kind of objects and users are in charge
|
||||
of connecting and disconnecting them from a signal to avoid crashes due to
|
||||
different lifetimes. On the other side, performance shouldn't be affected that
|
||||
much by the presence of such a signal handler.<br/>
|
||||
A signal handler can be used as a private data member without exposing any
|
||||
_publish_ functionality to the clients of a class. The basic idea is to impose a
|
||||
clear separation between the signal itself and its _sink_ class, that is a tool
|
||||
to be used to connect and disconnect listeners on the fly.
|
||||
|
||||
The API of a signal handler is straightforward. The most important thing is that
|
||||
it comes in two forms: with and without a collector. In case a signal is
|
||||
associated with a collector, all the values returned by the listeners can be
|
||||
literally _collected_ and used later by the caller. Otherwise it works just like
|
||||
a plain signal that emits events from time to time.<br/>
|
||||
|
||||
**Note**: collectors are allowed only in case of function types whose the return
|
||||
type isn't `void` for obvious reasons.
|
||||
|
||||
To create instances of signal handlers there exist mainly two ways:
|
||||
|
||||
```cpp
|
||||
// no collector type
|
||||
entt::SigH<void(int, char)> signal;
|
||||
|
||||
// explicit collector type
|
||||
entt::SigH<void(int, char), MyCollector<bool>> collector;
|
||||
```
|
||||
|
||||
As expected, they offer all the basic functionalities required to know how many
|
||||
listeners they contain (`size`) or if they contain at least a listener (`empty`)
|
||||
and even to swap two signal handlers (`swap`).
|
||||
|
||||
Besides them, there are member functions to use both to connect and disconnect
|
||||
listeners in all their forms by means of a sink:
|
||||
|
||||
```cpp
|
||||
void foo(int, char) { /* ... */ }
|
||||
|
||||
struct S {
|
||||
void bar(int, char) { /* ... */ }
|
||||
};
|
||||
|
||||
// ...
|
||||
|
||||
S instance;
|
||||
|
||||
signal.sink().connect<&foo>();
|
||||
signal.sink().connect<S, &S::bar>(&instance);
|
||||
|
||||
// ...
|
||||
|
||||
// disconnects a free function
|
||||
signal.sink().disconnect<&foo>();
|
||||
|
||||
// disconnect a specific member function of an instance ...
|
||||
signal.sink().disconnect<S, &S::bar>(&instance);
|
||||
|
||||
// ... or an instance as a whole
|
||||
signal.sink().disconnect(&instance);
|
||||
|
||||
// discards all the listeners at once
|
||||
signal.sink().disconnect();
|
||||
```
|
||||
|
||||
Once listeners are attached (or even if there are no listeners at all), events
|
||||
and data in general can be published through a signal by means of the `publish`
|
||||
member function:
|
||||
|
||||
```cpp
|
||||
signal.publish(42, 'c');
|
||||
```
|
||||
|
||||
To collect data, the `collect` member function should be used instead. Below is
|
||||
a minimal example to show how to use it:
|
||||
|
||||
```cpp
|
||||
struct MyCollector {
|
||||
std::vector<int> vec{};
|
||||
|
||||
bool operator()(int v) noexcept {
|
||||
vec.push_back(v);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
int f() { return 0; }
|
||||
int g() { return 1; }
|
||||
|
||||
// ...
|
||||
|
||||
entt::SigH<int(), MyCollector<int>> signal;
|
||||
|
||||
signal.sink().connect<&f>();
|
||||
signal.sink().connect<&g>();
|
||||
|
||||
MyCollector collector = signal.collect();
|
||||
|
||||
assert(collector.vec[0] == 0);
|
||||
assert(collector.vec[1] == 1);
|
||||
```
|
||||
|
||||
As shown above, a collector must expose a function operator that accepts as an
|
||||
argument a type to which the return type of the listeners can be converted.
|
||||
Moreover, it has to return a boolean value that is false to stop collecting
|
||||
data, true otherwise. This way one can avoid calling all the listeners in case
|
||||
it isn't necessary.
|
||||
|
||||
# Delegate
|
||||
|
||||
A delegate can be used as general purpose invoker with no memory overhead for
|
||||
free functions and member functions provided along with an instance on which
|
||||
to invoke them.<br/>
|
||||
It does not claim to be a drop-in replacement for an `std::function`, so do not
|
||||
expect to use it whenever an `std::function` fits well. However, it can be used
|
||||
to send opaque delegates around to be used to invoke functions as needed.
|
||||
|
||||
The interface is trivial. It offers a default constructor to create empty
|
||||
delegates:
|
||||
|
||||
```cpp
|
||||
entt::Delegate<int(int)> delegate{};
|
||||
```
|
||||
|
||||
All what is needed to create an instance is to specify the type of the function
|
||||
the delegate will _contain_, that is the signature of the free function or the
|
||||
member function one wants to assign to it.
|
||||
|
||||
Attempting to use an empty delegate by invoking its function call operator
|
||||
results in undefined behavior, most likely a crash actually. Before to use a
|
||||
delegate, it must be initialized.<br/>
|
||||
There exist two functions to do that, both named `connect`:
|
||||
|
||||
```cpp
|
||||
int f(int i) { return i; }
|
||||
|
||||
struct MyStruct {
|
||||
int f(int i) { return i }
|
||||
};
|
||||
|
||||
// bind a free function to the delegate
|
||||
delegate.connect<&f>();
|
||||
|
||||
// bind a member function to the delegate
|
||||
MyStruct instance;
|
||||
delegate.connect<MyStruct, &MyStruct::f>(&instance);
|
||||
```
|
||||
|
||||
It hasn't a `disconnect` counterpart. Instead, there exists a `reset` member
|
||||
function to clear it.<br/>
|
||||
The `empty` member function can be used to know if a delegate is empty:
|
||||
|
||||
```cpp
|
||||
const auto empty = delegate.empty();
|
||||
```
|
||||
|
||||
Finally, to invoke a delegate, the function call operator is the way to go as
|
||||
usual:
|
||||
|
||||
```cpp
|
||||
auto ret = delegate(42);
|
||||
```
|
||||
|
||||
Probably too much small and pretty poor of functionalities, but the delegate
|
||||
class can help in a lot of cases and it has shown that it is worth keeping it
|
||||
within the library.
|
||||
|
||||
# Event dispatcher
|
||||
|
||||
The event dispatcher class is designed so as to be used in a loop. It allows
|
||||
users both to trigger immediate events or to queue events to be published all
|
||||
together once per tick.<br/>
|
||||
This class shares part of its API with the one of the signal handler, but it
|
||||
doesn't require that all the types of events are specified when declared:
|
||||
|
||||
```cpp
|
||||
// define a general purpose dispatcher that works with naked pointers
|
||||
entt::Dispatcher dispatcher{};
|
||||
```
|
||||
|
||||
In order to register an instance of a class to a dispatcher, its type must
|
||||
expose one or more member functions of which the return types are `void` and the
|
||||
argument lists are `const E &`, for each type of event `E`.<br/>
|
||||
To ease the development, member functions that are named `receive` are
|
||||
automatically detected and have not to be explicitly specified when registered.
|
||||
In all the other cases, the name of the member function aimed to receive the
|
||||
event must be provided to the `connect` member function of the sink bound to the
|
||||
specific event:
|
||||
|
||||
```cpp
|
||||
struct AnEvent { int value; };
|
||||
struct AnotherEvent {};
|
||||
|
||||
struct Listener
|
||||
{
|
||||
void receive(const AnEvent &) { /* ... */ }
|
||||
void method(const AnotherEvent &) { /* ... */ }
|
||||
};
|
||||
|
||||
// ...
|
||||
|
||||
Listener listener;
|
||||
dispatcher.sink<AnEvent>().connect(&listener);
|
||||
dispatcher.sink<AnotherEvent>().connect<Listener, &Listener::method>(&listener);
|
||||
```
|
||||
|
||||
The `disconnect` member function follows the same pattern and can be used to
|
||||
selectively remove listeners:
|
||||
|
||||
```cpp
|
||||
dispatcher.sink<AnEvent>().disconnect(&listener);
|
||||
dispatcher.sink<AnotherEvent>().disconnect<Listener, &Listener::method>(&listener);
|
||||
```
|
||||
|
||||
The `trigger` member function serves the purpose of sending an immediate event
|
||||
to all the listeners registered so far. It offers a convenient approach that
|
||||
relieves users from having to create the event itself. Instead, it's enough to
|
||||
specify the type of event and provide all the parameters required to construct
|
||||
it.<br/>
|
||||
As an example:
|
||||
|
||||
```cpp
|
||||
dispatcher.trigger<AnEvent>(42);
|
||||
dispatcher.trigger<AnotherEvent>();
|
||||
```
|
||||
|
||||
Listeners are invoked immediately, order of execution isn't guaranteed. This
|
||||
method can be used to push around urgent messages like an _is terminating_
|
||||
notification on a mobile app.
|
||||
|
||||
On the other hand, the `enqueue` member function queues messages together and
|
||||
allows to maintain control over the moment they are sent to listeners. The
|
||||
signature of this method is more or less the same of `trigger`:
|
||||
|
||||
```cpp
|
||||
dispatcher.enqueue<AnEvent>(42);
|
||||
dispatcher.enqueue<AnotherEvent>();
|
||||
```
|
||||
|
||||
Events are stored aside until the `update` member function is invoked, then all
|
||||
the messages that are still pending are sent to the listeners at once:
|
||||
|
||||
```cpp
|
||||
// emits all the events of the given type at once
|
||||
dispatcher.update<MyEvent>();
|
||||
|
||||
// emits all the events queued so far at once
|
||||
dispatcher.update();
|
||||
```
|
||||
|
||||
This way users can embed the dispatcher in a loop and literally dispatch events
|
||||
once per tick to their systems.
|
||||
|
||||
# Event emitter
|
||||
|
||||
A general purpose event emitter thought mainly for those cases where it comes to
|
||||
working with asynchronous stuff.<br/>
|
||||
Originally designed to fit the requirements of
|
||||
[`uvw`](https://github.com/skypjack/uvw) (a wrapper for `libuv` written in
|
||||
modern C++), it was adapted later to be included in this library.
|
||||
|
||||
To create a custom emitter type, derived classes must inherit directly from the
|
||||
base class as:
|
||||
|
||||
```cpp
|
||||
struct MyEmitter: Emitter<MyEmitter> {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
The full list of accepted types of events isn't required. Handlers are created
|
||||
internally on the fly and thus each type of event is accepted by default.
|
||||
|
||||
Whenever an event is published, an emitter provides the listeners with a
|
||||
reference to itself along with a const reference to the event. Therefore
|
||||
listeners have an handy way to work with it without incurring in the need of
|
||||
capturing a reference to the emitter itself.<br/>
|
||||
In addition, an opaque object is returned each time a connection is established
|
||||
between an emitter and a listener, allowing the caller to disconnect them at a
|
||||
later time.<br/>
|
||||
The opaque object used to handle connections is both movable and copyable. On
|
||||
the other side, an event emitter is movable but not copyable by default.
|
||||
|
||||
To create new instances of an emitter, no arguments are required:
|
||||
|
||||
```cpp
|
||||
MyEmitter emitter{};
|
||||
```
|
||||
|
||||
Listeners must be movable and callable objects (free functions, lambdas,
|
||||
functors, `std::function`s, whatever) whose function type is:
|
||||
|
||||
```cpp
|
||||
void(const Event &, MyEmitter &)
|
||||
```
|
||||
|
||||
Where `Event` is the type of event they want to listen.<br/>
|
||||
There are two ways to attach a listener to an event emitter that differ
|
||||
slightly from each other:
|
||||
|
||||
* To register a long-lived listener, use the `on` member function. It is meant
|
||||
to register a listener designed to be invoked more than once for the given
|
||||
event type.<br/>
|
||||
As an example:
|
||||
|
||||
```cpp
|
||||
auto conn = emitter.on<MyEvent>([](const MyEvent &event, MyEmitter &emitter) {
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
The connection object can be freely discarded. Otherwise, it can be used later
|
||||
to disconnect the listener if required.
|
||||
|
||||
* To register a short-lived listener, use the `once` member function. It is
|
||||
meant to register a listener designed to be invoked only once for the given
|
||||
event type. The listener is automatically disconnected after the first
|
||||
invocation.<br/>
|
||||
As an example:
|
||||
|
||||
```cpp
|
||||
auto conn = emitter.once<MyEvent>([](const MyEvent &event, MyEmitter &emitter) {
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
The connection object can be freely discarded. Otherwise, it can be used later
|
||||
to disconnect the listener if required.
|
||||
|
||||
In both cases, the connection object can be used with the `erase` member
|
||||
function:
|
||||
|
||||
```cpp
|
||||
emitter.erase(conn);
|
||||
```
|
||||
|
||||
There are also two member functions to use either to disconnect all the
|
||||
listeners for a given type of event or to clear the emitter:
|
||||
|
||||
```cpp
|
||||
// removes all the listener for the specific event
|
||||
emitter.clear<MyEvent>();
|
||||
|
||||
// removes all the listeners registered so far
|
||||
emitter.clear();
|
||||
```
|
||||
|
||||
To send an event to all the listeners that are interested in it, the `publish`
|
||||
member function offers a convenient approach that relieves users from having to
|
||||
create the event:
|
||||
|
||||
```cpp
|
||||
struct MyEvent { int i; };
|
||||
|
||||
// ...
|
||||
|
||||
emitter.publish<MyEvent>(42);
|
||||
```
|
||||
|
||||
Finally, the `empty` member function tests if there exists at least either a
|
||||
listener registered with the event emitter or to a given type of event:
|
||||
|
||||
```cpp
|
||||
bool empty;
|
||||
|
||||
// checks if there is any listener registered for the specific event
|
||||
empty = emitter.empty<MyEvent>();
|
||||
|
||||
// checks it there are listeners registered with the event emitter
|
||||
empty = emitter.empty();
|
||||
```
|
||||
|
||||
In general, the event emitter is a handy tool when the derived classes _wrap_
|
||||
asynchronous operations, because it introduces a _nice-to-have_ model based on
|
||||
events and listeners that kindly hides the complexity behind the scenes. However
|
||||
it is not limited to such uses.
|
||||
Reference in New Issue
Block a user