entt Documentation

Gaming meets modern C++ - a fast and reliable entity component system (ECS) and much more

RAW Doc

Technical Documentation: skypjack/entt

ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference

Overview (`README.md`)








[!CAUTION]
EnTT has been a dream so far, we haven't found a single bug to date and it's
super easy to work with.

-- Every EnTT User Ever

EnTT is a header-only, tiny and easy to use library for game programming and
much more written in modern C++.
Among others, it's used
in Minecraft by Mojang, the
ArcGIS Runtime SDKs by Esri
and the amazing Ragdoll.
If you don't see your project in the list, please open an issue, submit a PR or
add the #entt tag to your topics! :+1:


Do you want to keep up with changes or do you have a question that
doesn't require you to open an issue?
Join the gitter channel and the
discord server, meet other users like you. The
more we are, the better for everyone.
Don't forget to check the
FAQs and the
wiki too. Your answers may already be
there.

Do you want to support EnTT? Consider becoming a
sponsor or making a
donation via PayPal.
Many thanks to these people and
special thanks to:



[!WARNING]
We apologize to our kind users for not posting updates every three days saying
that we are faster than three days ago and better than everyone else,
but consider it done. In the meantime, enjoy a library that just works
without having to constantly remind ourselves.

Table of Contents

Introduction

The entity-component-system (also known as ECS) is an architectural pattern
used mostly in game development. For further details:

This project started off as a pure entity-component system. Over time the
codebase has grown as more and more classes and functionalities were added.
Here is a brief, yet incomplete list of what it offers today:

  • Support for user implementations of the Standard Template Library.
  • Built-in RTTI system mostly similar to the standard one.
  • Incredibly fast entity-component system with its own pay for what you
    use
    policy, unconstrained component types with optional pointer stability and
    hooks for storage customization.
  • General purpose execution graph builder for optimal scheduling.
  • A built-in, non-intrusive and macro-free runtime reflection system.
  • Static polymorphism made simple and within everyone's reach.
  • A few homemade containers, like a sparse set based hash map.
  • All that is needed for resource management (cache, loaders, handles).
  • Delegates, signal handlers and a tiny event dispatcher.
  • And much more! Check out the
    wiki.

This list as well as the project are a work in progress. The whole API is fully
documented for those who are brave enough to read it.
Please, do note that all tools are also DLL-friendly now and run smoothly across
boundaries.

[!NOTE]
A lot of stuff was invented here, but we remain humble and let people copy
it without giving us credit.
The good and the bad of open source, but bragging is for others. Feel
free to get inspired.

One thing known to most is that EnTT is also used in Minecraft.
Given that the game is available literally everywhere, I can confidently say
that the library has been sufficiently tested on every platform that can come to
mind.

Code Example

cpp
#include <entt/entt.hpp>

struct position {
    float x;
    float y;
};

struct velocity {
    float dx;
    float dy;
};

void update(entt::registry &registry) {
    auto view = registry.view<const position, velocity>();

    // use a callback
    view.each([](const auto &pos, auto &vel) { /* ... */ });

    // use an extended callback
    view.each([](const auto entity, const auto &pos, auto &vel) { /* ... */ });

    // use a range-for
    for(auto [entity, pos, vel]: view.each()) {
        // ...
    }

    // use forward iterators and get only the components of interest
    for(auto entity: view) {
        auto &vel = view.get<velocity>(entity);
        // ...
    }
}

int main() {
    entt::registry registry;

    for(auto i = 0u; i < 10u; ++i) {
        const auto entity = registry.create();
        registry.emplace<position>(entity, i * 1.f, i * 1.f);
        if(i % 2 == 0) { registry.emplace<velocity>(entity, i * .1f, i * .1f); }
    }

    update(registry);
}

Motivation

I started developing EnTT for the wrong reason: my goal was to design an
entity-component system to beat another well known open source library both in
terms of performance and possibly memory usage.
In the end, I did it, but it wasn't very satisfying. Actually it wasn't
satisfying at all. The fastest and nothing more, fairly little indeed. When I
realized it, I tried hard to keep intact the great performance of EnTT and to
add all the features I wanted to see in my own library at the same time.

Nowadays, EnTT is finally what I was looking for: still faster than its
competitors, lower memory usage in the average case, a really good API and an
amazing set of features. And even more, of course.

[!Note]
If the EnTT API looks familiar, it's probably because it's been copied a
lot elsewhere (often without much ado).
Credit where credit is due though, the initial design was heavily inspired by
EntityX, another popular C++ library.

Benchmark

For what it's worth, you'll never see me trying to make other projects look
bad or offer dubious comparisons just to make this library seem cooler.
I leave this activity to others, if they enjoy it (and it seems that some people
actually like it). I prefer to make better use of my time.

If you are interested, you can compile the benchmark test in release mode (to
enable compiler optimizations, otherwise it would make little sense) by setting
the ENTT_BUILD_BENCHMARK option of CMake to ON, then evaluate yourself
whether you're satisfied with the results or not.

There are also a lot of projects out there that use EnTT as a basis for
comparison (this should already tell you a lot). Many of these benchmarks are
completely wrong, many others are simply incomplete, good at omitting some
information and using the wrong function to compare a given feature. Certainly
there are also good ones but they age quickly if nobody updates them, especially
when the library they are dealing with is actively developed.
Out of all of them, this seems like
the most up-to-date project and also covers a certain number of libraries. I
can't say exactly whether EnTT is used correctly or not. However, even if used
poorly, it should still give the reader an idea of where it's going to operate.

Integration

EnTT is a header-only library. This means that including the entt.hpp header
is enough to include the library as a whole and use it. For those who are
interested only in the entity-component system, consider to include the sole
entity/registry.hpp header instead.
It's a matter of adding the following line to the top of a file:

cpp
#include <entt/entt.hpp>

Use the line below to include only the entity-component system instead:

cpp
#include <entt/entity/registry.hpp>

Then pass the proper -I argument to the compiler to add the src directory to
the include paths.

Requirements

To be able to use EnTT, users must provide a full-featured compiler that
supports at least C++20.
The requirements below are mandatory to compile the tests and to extract the
documentation:

  • CMake version 3.28 or later.
  • Doxygen version 1.18 or later.

Alternatively, Bazel is also supported as a build system
(credits to zaucy who offered to maintain it).
In the documentation below I'll still refer to CMake, this being the official
build system of the library.

CMake

To use EnTT from a CMake project, just link an existing target to the
EnTT::EnTT alias.

The library offers everything you need for locating (as in find_package),
embedding (as in add_subdirectory), fetching (as in FetchContent) or using
it in many of the ways that you can think of and that involve CMake.
Covering all possible cases would require a treatise and not a simple README
file, but I'm confident that anyone reading this section also knows what it's
about and can use EnTT from a CMake project without problems.

Note that all install calls are guarded by the ENTT_INSTALL option to allow
using EnTT as a submodule without conflicting with user logic.
It is therefore necessary to set the option to true to take advantage of the
installation logic provided by this library.

Natvis support

When using CMake, just enable the option ENTT_INCLUDE_NATVIS and enjoy
it.
Otherwise, most of the tools are covered via Natvis and all files can be found
in the natvis subdirectory, divided by module.
If you spot errors or have suggestions, any contribution is welcome!

Packaging Tools

EnTT is available for some of the most known packaging tools. In particular:

  • Conan, the C/C++ Package
    Manager for Developers.

  • vcpkg, Microsoft VC++ Packaging
    Tool.
    You can download and install EnTT in just a few simple steps:

    text
    $ git clone https://github.com/Microsoft/vcpkg.git
    $ cd vcpkg
    $ ./bootstrap-vcpkg.sh
    $ ./vcpkg integrate install
    $ vcpkg install entt

    Or you can use the experimental feature to test the latest changes:

    text
    vcpkg install entt[experimental] --head

    The EnTT port in vcpkg is kept up to date by Microsoft team members and
    community contributors.
    If the version is out of date, please
    create an issue or pull request on the
    vcpkg repository.

  • Homebrew, the missing package
    manager for macOS.
    Available as a homebrew formula. Just type the following to install it:

    text
    brew install skypjack/entt/entt
  • build2, build toolchain for developing and packaging C
    and C++ code.
    In order to use the entt package in a build2
    project, add the following line or a similar one to the manifest file:

    text
    depends: entt ^3.0.0

    Also check that the configuration refers to a valid repository, so that the
    package can be found by build2:

    Both can be used with bpkg add-repo or added in a project
    repositories.manifest. See the official
    documentation
    for more details.

  • bzlmod, Bazel's external
    dependency management system.
    To use the entt module in a
    bazel project, add the following to your MODULE.bazel file:

    starlark
    bazel_dep(name = "entt", version = "3.16.0")

    EnTT will now be available as @entt (short for @entt//:entt) to be used
    in your cc_* rule deps.

Consider this list a work in progress and help me to make it longer if you like.

pkg-config

EnTT also supports pkg-config (for some definition of supports at least).
A suitable file called entt.pc is generated and installed in a proper
directory when running CMake.
This should also make it easier to use with tools such as Meson or similar.

Documentation

The documentation is based on doxygen. To build it:

text
$ cd build
$ cmake .. -DENTT_BUILD_DOCS=ON
$ make

The API reference is created in HTML format in the build/docs/html directory.
To navigate it with your favorite browser:

text
$ cd build
$ your_favorite_browser docs/html/index.html

The same version is also available online
for the latest release, that is the last stable tag.
Moreover, there exists a wiki dedicated
to the project where users can find all related documentation pages.

Tests

To compile and run the tests, EnTT requires googletest.
cmake downloads and compiles the library before compiling anything else. In
order to build the tests, set the CMake option ENTT_BUILD_TESTING to ON.

To build the most basic set of tests:

  • $ cd build
  • $ cmake -DENTT_BUILD_TESTING=ON ..
  • $ make
  • $ make test

Note that benchmarks are not part of this set.

EnTT in Action

EnTT is widely used in private and commercial applications. I cannot even
mention most of them because of some signatures I put on some documents time
ago. Fortunately, there are also people who took the time to implement open
source projects based on EnTT and did not hold back when it came to
documenting them.

Here you can find an
incomplete list of games, applications and articles that can be used as a
reference.

If you know of other resources out there that are about EnTT, feel free to
open an issue or a PR and I'll be glad to add them to the list.

Extensions

Community-maintained extensions for EnTT:

  • EnTTx - Adds hierarchies,
    prefabs, and observers for use cases such as networking and undo/redo.

If you know of other EnTT extensions, feel free to open an issue or a PR and
I'll be glad to add them to the list.

Contributors

Requests for features, PRs, suggestions and feedback are highly appreciated.

If you find you can help and want to contribute to the project with your
experience or you do want to get part of the project for some other reason, feel
free to contact me directly (you can find the mail in the
profile).
I can't promise that each and every contribution will be accepted, but I can
assure that I'll do my best to take them all as soon as possible.

If you decide to participate, please see the guidelines for
contributing
before to create issues or pull requests.
Take also a look at the
contributors list to
know who has participated so far.

License

Code and documentation Copyright (c) 2017-2026 Michele Caini.
Colorful logo Copyright (c) 2018-2021 Richard Caseres.

Code released under
the MIT license.
Documentation released under
CC BY 4.0.
All logos released under
CC BY-SA 4.0.


CMakeLists (`docs/CMakeLists.txt`)

Doxygen configuration (documentation)

find_package(Doxygen 1.18)

if(DOXYGEN_FOUND)
include(FetchContent)

text
FetchContent_Declare(
    doxygen-awesome-css
    URL https://github.com/jothepro/doxygen-awesome-css/archive/refs/heads/main.zip
)

FetchContent_MakeAvailable(doxygen-awesome-css)

set(DOXY_SOURCE_DIRECTORY ${EnTT_SOURCE_DIR}/src)
set(DOXY_CSS_DIRECTORY ${doxygen-awesome-css_SOURCE_DIR})
set(DOXY_DOCS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
set(DOXY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})

configure_file(doxy.in doxy.cfg @ONLY)

add_custom_target(
    docs ALL
    COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/doxy.cfg
    WORKING_DIRECTORY ${EnTT_SOURCE_DIR}
    VERBATIM
    SOURCES
        md/config.md
        md/container.md
        md/core.md
        md/entity.md
        md/faq.md
        md/graph.md
        md/lib.md
        md/links.md
        md/locator.md
        md/meta.md
        md/poly.md
        md/process.md
        md/reference.md
        md/resource.md
        md/signal.md
        md/stl.md
        doxy.in
)

if(ENTT_INSTALL)
    install(
        DIRECTORY ${DOXY_OUTPUT_DIRECTORY}/html
        DESTINATION share/${PROJECT_NAME}-${PROJECT_VERSION}/
    )
endif()

endif()


Md Config (`docs/md/config.md`)

Crash Course: configuration

Table of Contents

Introduction

EnTT has become almost completely customizable over time, in many
respects. These variables are just one of the many ways to customize how it
works.
In the vast majority of cases, users will have no interest in changing the
default parameters. For all other cases, the list of possible configurations
with which it is possible to adjust the behavior of the library at runtime can
be found below.

Definitions

All options are intended as parameters to the compiler (or user-defined macros
within the compilation units, if preferred).
Each parameter can result in internal library definitions. It is not recommended
to try to also modify these definitions, since there is no guarantee that they
will remain stable over time unlike the options below.

ENTT_USE_STL

Intended for testing purposes, it forces the use of built-in replacements of
some parts of the standard library that aren't always available otherwise.
EnTT detects these cases on its own, and users should never define this
variable explicitly. However, it's still possible if desired.

ENTT_NO_EXCEPTION

Define this variable without assigning any value to it to turn off exception
handling in EnTT.
This is roughly equivalent to setting the compiler flag -fno-exceptions but is
also limited to this library only.

ENTT_USE_ATOMIC

In general, EnTT does not offer primitives to support multi-threading. Many of
the features can be split over multiple threads without any explicit control and
the user is the one who knows if a synchronization point is required.
However, some internal static data shared between threads should be atomic when
using EnTT from multiple threads, even when dealing with local storage. Define
this macro without assigning any value to it to get the job done.

ENTT_ID_TYPE

entt::id_type is directly controlled by this definition and widely used within
the library.
By default, its type is std::uint32_t. However, users can define a different
default type if necessary.

ENTT_SPARSE_PAGE

It is known that the ECS module of EnTT is based on sparse sets. What is
less known perhaps is that the sparse arrays are paged to reduce memory
usage.
Default size of pages (that is, the number of elements they contain) is 4096 but
users can adjust it if appropriate. In all cases, the chosen value must be a
power of 2.

ENTT_PACKED_PAGE

As it happens with sparse arrays, packed arrays are also paginated. However, in
this case the aim is not to reduce memory usage but to have pointer stability
upon component creation.
Default size of pages (that is, the number of elements they contain) is 1024 but
users can adjust it if appropriate. In all cases, the chosen value must be a
power of 2.

ENTT_ASSERT

For performance reasons, EnTT does not use exceptions or any other control
structures. In fact, it offers many features that result in undefined behavior
if not used correctly.
To get around this, the library relies on a lot of asserts for the purpose of
detecting errors in debug builds. By default, it uses assert internally. Users
are allowed to overwrite its behavior by setting this variable.

ENTT_ASSERT_CONSTEXPR

Usually, an assert within a constexpr function is not a big deal. However, in
case of extreme customizations, it might be useful to differentiate.
For this purpose, EnTT introduces an admittedly badly named variable to make
the job easier in this regard. By default, this variable forwards its arguments
to ENTT_ASSERT.

ENTT_DISABLE_ASSERT

Assertions may in turn affect performance to an extent when enabled. Whether
ENTT_ASSERT and ENTT_ASSERT_CONSTEXPR are redefined or not, all asserts can
be disabled at once by means of this definition.
Note that ENTT_DISABLE_ASSERT takes precedence over the redefinition of the
other variables and is therefore meant to disable all controls no matter what.

ENTT_NO_ETO

In order to reduce memory consumption and increase performance, empty types are
never instantiated nor stored by the ECS module of EnTT.
Use this variable to treat these types like all others and therefore to create a
dedicated storage for them.

ENTT_NO_MIXIN

EnTT automatically assigns mixins to all storage types to support signaling
when creating, destroying, and modifying elements.
Mixins can have a (most likely negligible) cost in terms of performance and
compilation time. If unwanted, this macro suppresses automatic generation.

ENTT_STANDARD_CPP

EnTT mixes non-standard language features with others that are perfectly
compliant to offer some of its functionalities.
This definition prevents the library from using non-standard techniques, that
is, functionalities that are not fully compliant with the standard C++.
While there are no known portability issues at the time of this writing, this
should make the library fully portable anyway if needed.

Configuration injection

Configuration variables are provided via code or injected directly from the
outside via a dedicated file.
EnTT uses __has_include internally and looks for a specific path, namely
<entt/ext/config.h>. This can be provided by the user by setting the include
paths appropriately.
For example, CMake allows users to bind additional include directories to a
target with target_include_directories. See the test suite, and in particular
the config_ext test for a practical example.


Md Container (`docs/md/container.md`)

Crash Course: containers

Table of Contents

Introduction

The standard C++ library offers a wide range of containers and adaptors already.
It is really difficult to do better (although it is very easy to do worse, as
many examples available online demonstrate).
EnTT does not try in any way to replace what is offered by the standard. Quite
the opposite, given the widespread use that is made of standard containers.
However, the library also tries to fill a gap in features and functionalities by
making available some containers and adaptors initially developed for internal
use.

This section of the library is likely to grow larger over time. However, for the
moment it is quite small and mainly aimed at satisfying some internal
needs.
For all containers and adaptors made available, full test coverage and stability
over time is guaranteed as usual.

Containers

Dense map

The dense map made available in EnTT is a hash map that aims to return a
packed array of elements, so as to reduce the number of jumps in memory during
iterations.
The implementation is based on sparse sets and each bucket is identified by an
implicit list within the packed array itself.

The interface is very close to its counterpart in the standard library, that is,
the std::unordered_map class.
However, both local and non-local iterators returned by a dense map belong to
the input iterator category although they respectively model the concepts of a
forward iterator type and a random access iterator type.
This is because they return a pair of references rather than a reference to a
pair. In other words, dense maps return a so called proxy iterator the value
type of which is:

  • std::pair<const Key &, Type &> for non-const iterator types.
  • std::pair<const Key &, const Type &> for const iterator types.

This is quite different from what any standard library map returns and should be
taken into account when looking for a drop-in replacement.

Dense set

The dense set made available in EnTT is a hash set that aims to return a
packed array of elements, so as to reduce the number of jumps in memory during
iterations.
The implementation is based on sparse sets and each bucket is identified by an
implicit list within the packed array itself.

The interface is in all respects similar to its counterpart in the standard
library, that is, the std::unordered_set class.
However, this type of set also supports reverse iteration and therefore offers
all the functions necessary for the purpose (such as rbegin and rend).

Adaptors

Table

The basic_table class is a container adaptor which manages multiple sequential
containers together, treating them as different columns of the same table.
The table alias allows users to provide only the types to handle, using
std::vector as the default sequential container.

Only a small set of functions is provided, although very close to what the API
of the std::vector class offers.
The internal implementation is purposely supported by a tuple of containers
rather than a container of tuples. The purpose is to allow efficient access to
single columns and not just access to the entire data set of the table.

When a row is accessed, all data is returned in the form of a tuple containing
(possibly const) references to the elements of the row itself.
Similarly, when a table is iterated, tuples of references to table elements are
returned for each row.


Md Core (`docs/md/core.md`)

Crash Course: core functionalities

Table of Contents

Introduction

EnTT comes with a bunch of core functionalities mostly used by the other parts
of the library.
Many of these tools are also useful in everyday work. Therefore, it is worth
describing them so as not to reinvent the wheel in case of need.

Any as in any type

EnTT offers its own any type. It may seem redundant considering that C++17
introduced std::any, but it is not (hopefully).
First of all, the type returned by an std::any is a const reference to an
std::type_info, an implementation defined class that is not something everyone
wants to see in a software. Furthermore, there is no way to bind it to the type
system of the library and therefore with its integrated RTTI support.

The any API is very similar to that of its most famous counterpart, mainly
because this class serves the same purpose of being an opaque container for any
type of value.
Instances also minimize the number of allocations by relying on a well known
technique called small buffer optimization and a fake vtable.

Creating an object of the any type, whether empty or not, is trivial:

cpp
// an empty container
entt::any empty{};

// a container for an int
entt::any any{0};

// in place type construction
entt::any in_place_type{std::in_place_type<int>, 42};

// take ownership of already existing, dynamically allocated objects
entt::any in_place{std::in_place, std::make_unique<int>(42).release()};

Alternatively, the make_any function serves the same purpose. It requires to
always be explicit about the type and does not support taking ownership:

cpp
entt::any any = entt::make_any<int>(42);

In all cases, the any class takes the burden of destroying the contained
element when required, regardless of the storage strategy used for the specific
object.
Furthermore, an instance of any is not tied to an actual type. Therefore, the
wrapper is reconfigured when it is assigned a new object of a type other than
the one it contains.

There is also a way to directly assign a value to the variable contained by an
entt::any, without necessarily replacing it. This is especially useful when
the object is used in aliasing mode, as described below:

cpp
entt::any any{42};
entt::any value{3};

// assigns by copy
any.assign(value);

// assigns by move
any.assign(std::move(value));

The any class performs a check on the type information and whether or not the
original type was copy or move assignable, as appropriate.
In all cases, the assign function returns a boolean value that is true in case
of success and false otherwise.

When in doubt about the type of object contained, the type member function
returns a const reference to the type_info associated with its element, or
type_id<void>() if the container is empty.
The type is also used internally when comparing two any objects:

cpp
if(any == empty) { /* ... */ }

In this case, before proceeding with a comparison, it is verified that the
type of the two objects is actually the same.
Refer to the EnTT type system documentation for more details about how
type_info works and the possible risks of a comparison.

A particularly interesting feature of this class is that it can also be used as
an opaque container for const and non-const references:

cpp
int value = 42;

entt::any any{std::in_place_type<int &>(value)};
entt::any cany = entt::make_any<const int &>(value);
entt::any fwd = entt::forward_as_any(value);

any.emplace<const int &>(value);

In other words, whenever any is explicitly told to construct an alias, it
acts as a pointer to the original instance rather than making a copy of it or
moving it internally. The contained object is never destroyed, and users must
ensure that its lifetime exceeds that of the container.
Similarly, it is possible to create non-owning copies of any from an existing
object:

cpp
// aliasing constructor
entt::any ref = other.as_ref();

In this case, it does not matter if the original container actually holds an
object or is as a reference for unmanaged elements already. The new instance
thus created does not create copies and only serves as a reference for the
original item.

It is worth mentioning that, while everything works transparently when it comes
to non-const references, there are some exceptions when it comes to const
references.
In particular, the data member function invoked on a non-const instance of
any that wraps a const reference returns a null pointer in all cases.

To cast an instance of any to a type, the library offers a set of any_cast
functions in all respects similar to their most famous counterparts.
The only difference is that, in the case of EnTT, they will not raise
exceptions but will only trigger an assert in debug mode, otherwise resulting in
undefined behavior in case of misuse in release mode.

Small buffer optimization

The any class uses a technique called small buffer optimization to reduce
the number of allocations where possible.
The default reserved size for an instance of any is sizeof(double[2]).
However, this is also configurable if needed. In fact, any is defined as an
alias for basic_any<Len>, where Len is the size above.
Users can easily set a custom size or define their own aliases:

cpp
using my_any = entt::basic_any<sizeof(double[4])>;

This feature, in addition to allowing the choice of a size that best suits the
needs of an application, also offers the possibility of forcing dynamic creation
of objects during construction.
In other terms, if the size is 0, any suppresses the small buffer optimization
and always dynamically allocates objects (except for aliasing cases).

Alignment requirement

The alignment requirement is optional and by default the most stringent (the
largest) for any object whose size is at most equal to the one provided.
It is provided as an optional second parameter following the desired size for
the internal storage:

cpp
using my_any = entt::basic_any<sizeof(double[4]), alignof(double[4])>;

The basic_any class template inspects the alignment requirements in each case,
even when not provided and may decide not to use the small buffer optimization
in order to meet them.

Bit

Some general purpose utilities, such as the fast module function:

cpp
const std::size_t result = entt::fast_mod(value, modulus);

Where modulus is necessarily a power of two. This type of operation is far
superior in terms of performance to the basic modulus and for this reason
preferred in many areas.

Compressed pair

Primarily designed for internal use and far from being feature complete, the
compressed_pair class does exactly what it promises: it tries to reduce the
size of a pair by exploiting Empty Base Class Optimization (or EBCO).
This class is not a drop-in replacement for std::pair. However, it offers
enough functionalities to be a good alternative for when reducing memory usage
is more important than having some cool and probably useless feature.

Although the API is very close to that of std::pair (apart from the fact that
the template parameters are inferred from the constructor and therefore there is
no entt::make_compressed_pair), the major difference is that first and
second are functions for implementation requirements:

cpp
entt::compressed_pair pair{0, 3.};
pair.first() = 42;

There is not much to describe then. It is recommended to rely on documentation
and intuition. At the end of the day, it is just a pair and nothing more.

Enum as bitmask

Sometimes it is useful to be able to use enums as bitmasks. However, enum
classes are not really suitable for the purpose. Main problem is that they do
not convert implicitly to their underlying type.
The choice is then between using old-fashioned enums (with all their problems
that I do not want to discuss here) or writing ugly code.

Fortunately, there is also a third way: adding enough operators in the global
scope to treat enum classes as bitmasks transparently.
The ultimate goal is to write code like the following (or maybe something more
meaningful, but this should give a grasp and remain simple at the same time):

cpp
enum class my_flag {
    unknown = 0x01,
    enabled = 0x02,
    disabled = 0x04
};

const my_flag flags = my_flag::enabled;
const bool is_enabled = !!(flags & my_flag::enabled);

The problem with adding all operators to the global scope is that these come
into play even when not required, with the risk of introducing errors that are
difficult to deal with.
However, C++ offers enough tools to get around this problem. In particular, the
library requires users to register the enum classes for which bitmask support
should be enabled:

cpp
template<>
struct entt::enum_as_bitmask<my_flag>
    : std::true_type
{};

This is handy when dealing with enum classes defined by third party libraries
and over which the user has no control. However, it is also verbose and can be
avoided by adding a specific value to the enum class itself:

cpp
enum class my_flag {
    unknown = 0x01,
    enabled = 0x02,
    disabled = 0x04,
    _entt_enum_as_bitmask
};

In this case, there is no need to specialize the enum_as_bitmask traits, since
EnTT automatically detects the flag and enables the bitmask support.
Once the enum class is registered (in one way or the other), the most common
operators such as &, | but also &= and |= are available for use.

Refer to the official documentation for the full list of operators.

Hashed strings

Hashed strings are human-readable identifiers in the codebase that turn into
numeric values at runtime, thus without affecting performance.
The class has an implicit constexpr constructor that chews a bunch of
characters. Once created, one can get the original string by means of the data
member function or convert the instance into a number.
A hashed string is well suited wherever a constant expression is required. No
string-to-number conversion will take place at runtime if used carefully.

Example of use:

cpp
auto load(entt::hashed_string::hash_type resource) {
    // uses the numeric representation of the resource to load and return it
}

auto resource = load(entt::hashed_string{"gui/background"});

There is also a user defined literal dedicated to hashed strings to make them
more user-friendly:

cpp
using namespace entt::literals;
constexpr auto str = "text"_hs;

User defined literals in EnTT are enclosed in the entt::literals namespace.
Therefore, the entire namespace or selectively the literal of interest must be
explicitly included before each use, a bit like std::literals.
The class also offers the necessary functionalities to create hashed strings at
runtime:

cpp
std::string orig{"text"};

// create a full-featured hashed string...
entt::hashed_string str{orig.c_str()};

// ... or compute only the unique identifier
const auto hash = entt::hashed_string::value(orig.c_str());

This possibility should not be exploited in tight loops, since the computation
takes place at runtime and no longer at compile-time. It could therefore affect
performance to some degrees.

Wide characters

The hashed_string class is an alias for basic_hashed_string<char>. To use
the C++ type for wide character representations, there exists also the alias
hashed_wstring for basic_hashed_string<wchar_t>.
In this case, the user defined literal to use to create hashed strings on the
fly is _hws:

cpp
constexpr auto str = L"text"_hws;

The hash type of hashed_wstring is the same as its counterpart.

Conflicts

The hashed string class uses FNV-1a internally to hash strings. Because of the
pigeonhole principle, conflicts are possible. This is a fact.
There is no silver bullet to solve the problem of conflicts when dealing with
hashing functions. In this case, the best solution is likely to give up. That is
all.
After all, human-readable unique identifiers are not 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.

Iterators

Writing and working with iterators is not always easy. More often than not it
also leads to duplicated code.
EnTT tries to overcome this problem by offering some utilities designed to
make this hard work easier.

Input iterator pointer

When writing an input iterator that returns in-place constructed values if
dereferenced, it is not always straightforward to figure out what value_type
is and how to make it behave like a full-fledged pointer.
Conversely, it would be very useful to have an operator-> available on the
iterator itself that always works without too much complexity.

The input iterator pointer is meant for this. It is a small class that wraps the
in-place constructed value and adds some functions on top of it to make it
suitable for use with input iterators:

cpp
struct iterator_type {
    using value_type = std::pair<first_type, second_type>;
    using pointer = input_iterator_pointer<value_type>;
    using reference = value_type;
    using difference_type = std::ptrdiff_t;
    using iterator_category = std::input_iterator_tag;

    // ...
}

The library makes extensive use of this class internally. In many cases, the
value_type of the returned iterators is just an input iterator pointer.

Iota iterator

Waiting for C++20, this iterator accepts an integral value and returns all
elements in a certain range:

cpp
entt::iota_iterator first{0};
entt::iota_iterator last{100};

for(; first != last; ++first) {
    int value = *first;
    // ...
}

In the future, views will replace this class. Meanwhile, the library makes some
interesting uses of it when a range of integral values is to be returned to the
user.

Iterable adaptor

Typically, a container class provides begin and end member functions (with
their const counterparts) for iteration.
However, it can happen that a class offers multiple iteration methods or allows
users to iterate different sets of elements.

The iterable adaptor is a utility class that makes it easier to use and access
data in this case.
It accepts a couple of iterators (or an iterator and a sentinel) and offers an
iterable object with all the expected methods like begin, end and whatnot.

The library uses this class extensively.
Think for example of views, which can be iterated to access entities but also
offer a method for obtaining an iterable object that returns tuples of entities
and components at once.
Another example is the registry class which allows users to iterate its storage
by returning an iterable object for the purpose.

Memory

There are a handful of tools within EnTT to interact with memory in one way or
another.
Some are geared towards simplifying the implementation of (internal or external)
allocator aware containers. Others are designed to help the developer with
everyday problems.

The former are very specific and for niche problems. For example, there are
tools designed to help forget the meaning of acronyms like POCCA, POCMA or
POCS.
I will not describe them here in detail. Instead, I recommend reading the inline
documentation to those interested in the subject.

Allocator aware unique pointers

A nasty thing in C++ (at least up to C++20) is the fact that shared pointers
support allocators while unique pointers do not.
There is a proposal at the moment that also shows (among the other things) how
this can be implemented without any compiler support.

The allocate_unique function follows this proposal, making a virtue out of
necessity:

cpp
std::unique_ptr<my_type, entt::allocation_deleter<allocator_type>> ptr = entt::allocate_unique<my_type>(allocator, arguments);

Although the internal implementation is slightly different from what is proposed
for the standard, this function offers an API that is a drop-in replacement for
the same feature.

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).

Keys are integral values (easily obtained by hashed strings), values are basic
types like ints or bools. Values of different types can be associated with
each key, even more than one at a time.
Because of this, one should pay attention to use the same type both during an
assignment and when trying to read back the data. Otherwise, there is the risk
to incur in unexpected results.

Example of use:

cpp
entt::monostate<entt::hashed_string{"mykey"}>{} = true;
entt::monostate<"mykey"_hs>{} = 42;

// ...

const bool b = entt::monostate<"mykey"_hs>{};
const int i = entt::monostate<entt::hashed_string{"mykey"}>{};

Type support

EnTT provides some basic information about types of all kinds.
It also offers additional features that are not yet available in the standard
library or that will never be.

Built-in RTTI support

Runtime type identification support (or RTTI) is one of the most frequently
disabled features in the C++ world, especially in the gaming sector. Regardless
of the reasons for this, it is often a shame not to be able to rely on opaque
type information at runtime.
The library tries to fill this gap by offering a built-in system that does not
serve as a replacement but comes very close to being one and offers similar
information to that provided by its counterpart.

Basically, the whole system relies on a handful of classes. In particular:

  • The unique sequential identifier associated with a given type:

    cpp
    auto index = entt::type_index<a_type>::value();

    The returned value is not guaranteed to be stable across different runs.
    However, it can be very useful as index in associative and unordered
    associative containers or for positional accesses in a vector or an array.

    An external generator can also be used if needed. In fact, type_index can be
    specialized by type or constrained with a concept in order to allow more
    refined specializations such as:

    cpp
    template<typename Type>
    requires requires { { Type::index() } -> std::same_as<entt::id_type>; }
    struct entt::type_index<Type> {
        static entt::id_type value() noexcept {
            return Type::index();
        }
    };

    Indexes must be sequentially generated in this case.
    The tool is widely used within EnTT. Generating indices not sequentially
    would break an assumption and would likely lead to undesired behaviors.

  • The hash value associated with a given type:

    cpp
    auto hash = entt::type_hash<a_type>::value();

    In general, the value function exposed by type_hash is also constexpr
    but this is not guaranteed for all compilers and platforms (although it is
    valid with the most well-known and popular ones).

    This function can use non-standard features of the language for its own
    purposes. This makes it possible to provide compile-time identifiers that
    remain stable across different runs.
    Users can prevent the library from using these features by means of the
    ENTT_STANDARD_CPP definition. In this case, there is no guarantee that
    identifiers remain stable across executions. Moreover, they are generated
    at runtime and are no longer a compile-time thing.

    As it happens with type_index, also type_hash can be specialized or
    constrained with a concept in order to customize its behavior globally or on a
    per-type or per-traits basis.

  • The name associated with a given type:

    cpp
    auto name = entt::type_name<a_type>::value();

    This value is extracted from some information generally made available by the
    compiler in use. Therefore, it may differ depending on the compiler and may be
    empty in the event that this information is not available.
    For example, given the following class:

    cpp
    struct my_type { /* ... */ };

    The name is my_type when compiled with GCC or CLang and struct my_type
    when MSVC is in use.
    Most of the time the name is also retrieved at compile-time and is therefore
    always returned through an std::string_view. Users can easily access it and
    modify it as needed, for example by removing the word struct to normalize
    the result. EnTT does not do this for obvious reasons, since it would be
    creating a new string at runtime otherwise.

    This function can use non-standard features of the language for its own
    purposes. Users can prevent the library from using these features by means of
    the ENTT_STANDARD_CPP definition. In this case, the name is just empty.

    As it happens with type_index, also type_name can be specialized or
    constrained with a concept in order to customize its behavior globally or on a
    per-type or per-traits basis.

These are then combined into utilities that aim to offer an API that is somewhat
similar to that made available by the standard library.

Type info

The type_info class is not a drop-in replacement for std::type_info but can
provide similar information which are not implementation defined and do not
require to enable RTTI.
Therefore, they can sometimes be even more reliable than those obtained
otherwise.

Its type defines an opaque class that is also copyable and movable.
Objects of this type are generally returned by the type_id functions:

cpp
// by type
auto info = entt::type_id<a_type>();

// by value
auto other = entt::type_id(42);

All elements thus received are nothing more than const references to instances
of type_info with static storage duration.
This is convenient for saving the entire object aside for the cost of a pointer.
However, nothing prevents from constructing type_info objects directly:

cpp
entt::type_info info{std::in_place_type<int>};

These are the information made available by type_info:

  • The index associated with a given type:

    cpp
    auto idx = entt::type_id<a_type>().index();

    This is also an alias for the following:

    cpp
    auto idx = entt::type_index<std::remove_cvref_t<a_type>>::value();
  • The hash value associated with a given type:

    cpp
    auto hash = entt::type_id<a_type>().hash();

    This is also an alias for the following:

    cpp
    auto hash = entt::type_hash<std::remove_cvref_t<a_type>>::value();
  • The name associated with a given type:

    cpp
    auto name = entt::type_id<my_type>().name();

    This is also an alias for the following:

    cpp
    auto name = entt::type_name<std::remove_cvref_t<a_type>>::value();

Where all accessed features are available at compile-time, the type_info class
is also fully constexpr. However, this cannot be guaranteed in advance and
depends mainly on the compiler in use and any specializations of the classes
described above.

Almost unique identifiers

Since the default non-standard, compile-time implementation of type_hash makes
use of hashed strings, it may happen that two types are assigned the same hash
value.
In fact, although this is quite rare, it is not entirely excluded.

Another case where two types are assigned the same identifier is when classes
from different contexts (for example two or more libraries loaded at runtime)
have the same fully qualified name. In this case, type_name returns the same
value for the two types.
Fortunately, there are several easy ways to deal with this:

  • The most trivial one is to define the ENTT_STANDARD_CPP macro. Runtime
    identifiers do not suffer from the same problem in fact. However, this
    solution does not work well with a plugin system, where the libraries are not
    linked.

  • Another possibility is to specialize the type_name class for one of the
    conflicting types, in order to assign it a custom identifier. This is probably
    the easiest solution that also preserves the feature of the tool.

  • A fully customized identifier generation policy (based for example on enum
    classes or preprocessing steps) may represent yet another option.

These are just some examples of possible approaches to the problem, but there
are many others. As already mentioned above, since users have full control over
their types, this problem is in any case easy to solve and should not worry too
much.
In all likelihood, it will never happen to run into a conflict anyway.

Type traits

A handful of utilities and traits not present in the standard template library
but which can be useful in everyday life.
This list is not exhaustive and contains only some of the most useful
classes. Refer to the inline documentation for more information on the features
offered by this module.

Size of

The standard operator sizeof complains if users provide it with functions or
incomplete types. On the other hand, it is guaranteed that its result is always
non-zero, even if applied to an empty class type.
This small class combines the two and offers an alternative to sizeof that
works under all circumstances, returning zero if the type is not supported:

cpp
const auto size = entt::size_of_v<void>;

Is applicable

The standard library offers the great std::is_invocable trait in several
forms. This takes a function type and a series of arguments and returns true if
the condition is satisfied.
Moreover, users are also provided with std::apply, a tool for combining
invocable elements and tuples of arguments.

It would therefore be a good idea to have a variant of std::is_invocable that
also accepts its arguments in the form of a tuple-like type, so as to complete
the offer:

cpp
constexpr bool result = entt::is_applicable<Func, std::tuple<a_type, another_type>>;

This trait is built on top of std::is_invocable and does nothing but unpack a
tuple-like type and simplify the code at the call site.

Constness as

A utility to easily transfer the constness of a type to another type:

cpp
// type is const dst_type because of the constness of src_type
using type = entt::constness_as_t<dst_type, const src_type>;

The trait is subject to the rules of the language. For example, transferring
constness between references will not give the desired effect.

Member class type

The auto template parameter introduced with C++17 made it possible to simplify
many class templates and template functions but also made the class type opaque
when members are passed as template arguments.
The purpose of this utility is to extract the class type in a few lines of code:

cpp
template<typename Member>
using clazz = entt::member_class_t<Member>;

N-th argument

A utility to quickly find the n-th argument of a function, member function or
data member (for blind operations on opaque types):

cpp
using type = entt::nth_argument_t<1u, decltype(&clazz::member)>;

Disambiguation of overloaded functions is the responsibility of the user, should
it be needed.

Integral constant

Since std::integral_constant may be annoying because of its form that requires
to specify both a type and a value of that type, there is a more user-friendly
shortcut for the creation of integral constants.
This shortcut is the alias template entt::integral_constant:

cpp
constexpr auto constant = entt::integral_constant<42>;

Among the other uses, when combined with a hashed string it helps to define tags
as human-readable names where actual types would be required otherwise:

cpp
constexpr auto enemy_tag = entt::integral_constant<"enemy"_hs>;
registry.emplace<enemy_tag>(entity);

Tag

Type id_type is very important and widely used in EnTT. Therefore, there is
a more user-friendly shortcut for the creation of constants based on it.
This shortcut is the alias template entt::tag.

If used in combination with hashed strings, it helps to use human-readable names
where types would be required otherwise. As an example:

cpp
registry.emplace<entt::tag<"enemy"_hs>>(entity);

However, this is not the only permitted use. Literally any value convertible to
id_type is a good candidate, such as the named constants of an unscoped enum.

Type list and value list

There is no respectable library where the much desired type list can be
missing.
EnTT is no exception and provides (making extensive use of it internally) the
type_list type, in addition to its value_list counterpart dedicated to
non-type template parameters.

Here is a (possibly incomplete) list of the functionalities that come with a
type list:

  • type_list_element[_t] to get the N-th element of a type list.
  • type_list_index[_v] to get the index of a given element of a type list.
  • type_list_cat[_t] and a handy operator+ to concatenate type lists.
  • type_list_unique[_t] to remove duplicate types from a type list.
  • type_list_contains[_v] to know if a type list contains a given type.
  • type_list_diff[_t] to remove types from type lists.
  • type_list_transform[_t] to transform a range and create another type list.

I am also pretty sure that more and more utilities will be added over time as
needs become apparent.
Many of these functionalities also exist in their version dedicated to value
lists. We therefore have value_list_element[_v] as well as
value_list_cat[_t]and so on.

Unique sequential identifiers

Sometimes it is useful to be able to give unique, sequential numeric identifiers
to types either at compile-time or runtime.
There are plenty of different solutions for this out there, and I could have
used one of them. However, I decided to spend my time to define a couple of
tools that fully embrace what modern C++ has to offer.

Compile-time generator

To generate sequential numeric identifiers at compile-time, EnTT offers the
ident class template:

cpp
// defines the identifiers for the given types
using id = entt::ident<a_type, another_type>;

// ...

switch(a_type_identifier) {
case id::value<a_type>:
    // ...
    break;
case id::value<another_type>:
    // ...
    break;
default:
    // ...
}

This is what this class template has to offer: a value inline variable that
contains a numeric 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
stable across different runs. If used in a production environment where a type
needs to be removed, a placeholder can help to leave the other identifiers the
same:

cpp
template<typename>
struct ignore_type {};

using id = entt::ident<
    a_type_still_valid,
    ignore_type<no_longer_valid_type>,
    another_type_still_valid
>;

Perhaps a bit ugly to see in a codebase, but it gets the job done at least.

Runtime generator

The family class template helps to generate sequential numeric identifiers for
types at runtime:

cpp
// defines a custom generator
using id = entt::family<struct my_tag>;

// ...

const auto a_type_id = id::value<a_type>;
const auto another_type_id = id::value<another_type>;

This is what a family has to offer: a value inline variable that contains a
numeric identifier for the given type.
The generator is customizable, so as to get different sequences for different
purposes if needed.

Identifiers are not guaranteed to be stable across different runs. Indeed it
mostly depends on the flow of execution.

Utilities

It is not possible to escape the temptation to add utilities of some kind to a
library. In fact, EnTT also provides a handful of tools to simplify the
life of developers:

  • entt::overload: a tool to disambiguate different overloads from their
    function type. It works with both free and member functions.
    Consider the following definition:

    cpp
    struct clazz {
        void bar(int) {}
        void bar() {}
    };

    This utility can be used to get the right overload as:

    cpp
    auto *member = entt::overload<void(int)>(&clazz::bar);

    The line above is literally equivalent to:

    cpp
    auto *member = static_cast<void(clazz:: *)(int)>(&clazz::bar);

    Just easier to read and shorter to type.

  • entt::overloaded: a small class template used to create a new type with an
    overloaded operator() from a bunch of lambdas or functors.
    As an example:

    cpp
    entt::overloaded func{
        [](int value) { /* ... */ },
        [](char value) { /* ... */ }
    };
    
    func(42);
    func('c');

    Rather useful when doing metaprogramming and having to pass to a function a
    callable object that supports multiple types at once.

  • entt::y_combinator: this is a C++ implementation of the y-combinator.
    If it is not clear what it is, there is probably no need for this
    utility.
    Below is a small example to show its use:

    cpp
    entt::y_combinator gauss([](const auto &self, auto value) -> unsigned int {
        return value ? (value + self(value-1u)) : 0;
    });
    
    const auto result = gauss(3u);

    Maybe convoluted at first glance but certainly effective. Unfortunately,
    the language does not make it possible to do much better.

This is a rundown of the (actually few) utilities made available by EnTT. The
list will probably grow over time, but the size of each will remain rather
small, as has been the case so far.


Md Entity (`docs/md/entity.md`)

Crash Course: entity-component system

Table of Contents

Introduction

EnTT offers a header-only, tiny and easy to use entity-component system module
written in modern C++.
The entity-component-system (also known as ECS) is an architectural pattern
used mostly in game development.

Design decisions

Type-less and bitset-free

The library implements a sparse set based model that does not require users to
specify the set of components neither at compile-time nor at runtime.
This is why users can instantiate the core class simply like:

cpp
entt::registry registry;

In place of its more annoying and error-prone counterpart:

cpp
entt::registry<comp_0, comp_1, ..., comp_n> registry;

Furthermore, it is not necessary to announce the existence of a component type.
When the time comes, just use it and that is all.

Build your own

The ECS module (as well as the rest of the library) is designed as a set of
containers that are used as needed, just like a vector or any other container.
It does not attempt in any way to take over on the user codebase, nor to control
its main loop or process scheduling.
Unlike other more or less well known models, it also makes use of independent
pools that are extended via static mixins. The built-in signal support is an
example of this flexible design: defined as a mixin, it is easily disabled if
not needed. Similarly, the storage class has a specialization that shows how
everything is customizable down to the smallest detail.

Pay per use

Everything is designed around the principle that users only have to pay for what
they want.

When it comes to using an entity-component system, the tradeoff is usually
between performance and memory usage. The faster it is, the more memory it uses.
Even worse, some approaches tend to heavily affect other functionalities like
the construction and destruction of components to favor iterations, even when it
is not strictly required. In fact, slightly worse performance along non-critical
paths are the right price to pay to reduce memory usage and have overall better
performance.
EnTT follows a completely different approach. It gets the best out from the
basic data structures and gives users the possibility to pay more for higher
performance where needed.

All or nothing

As a rule of thumb, a T ** pointer (or whatever a custom pool returns) is
always available to directly access all the instances of a given component type
T.
This is one of the corner stones of the library. Many of the tools offered are
designed around this need and give the possibility to get this information.

Vademecum

The entt::entity type implements the concept of entity identifier. An entity
(the E of an ECS) is an opaque element to use as-is. Inspecting it is not
recommended since its format can change in future.
Components (the C of an ECS) are of any type, without any constraints, not
even that of being movable. No need to register them nor their types.
Systems (the S of an ECS) are plain functions, functors, lambdas and so on.
It is not required to announce them in any case and have no requirements.

The next sections go into detail on how to use the entity-component system part
of the EnTT library.
This module is likely larger than what is described below. For more details,
please refer to the inline documentation.

The Registry, the Entity and the Component

A registry stores and manages entities (or identifiers) and components.
The class template basic_registry lets users decide what the preferred type to
represent an entity is. Because std::uint32_t is large enough for almost any
case, there also exists the enum class entt::entity that wraps it and the
alias entt::registry for entt::basic_registry<entt::entity>.

Entities are represented by entity identifiers. An entity identifier contains
information about the entity itself and its version.
User defined identifiers are allowed as enum classes and class types that define
an entity_type member of type std::uint32_t or std::uint64_t.

A registry is used both to construct and to destroy entities:

cpp
// constructs a naked entity with no components and returns its identifier
auto entity = registry.create();

// destroys an entity and all its components
registry.destroy(entity);

The create member function also accepts a hint. Moreover, it has an overload
that gets two iterators to use to generate many entities at once efficiently.
Similarly, the destroy member function also works with a range of entities:

cpp
// destroys all the entities in a range
auto view = registry.view<a_component, another_component>();
registry.destroy(view.begin(), view.end());

In addition to offering an overload to force the version upon destruction.
This function removes all components from an entity before releasing it. There
also exists a lighter alternative that does not query component pools, for use
with orphaned entities:

cpp
// releases an orphaned identifier
registry.release(entity);

As with the destroy function, also in this case entity ranges are supported
and it is possible to force a version.

In both cases, when an identifier is released, the registry can freely reuse it
internally. In particular, the version of an entity is increased (unless the
overload that forces a version is used instead of the default one).
Users can then test identifiers by means of a registry:

cpp
// returns true if the entity is still valid, false otherwise
bool b = registry.valid(entity);

// gets the actual version for the given entity
auto curr = registry.current(entity);

Or inspect them using some functions meant for parsing an identifier as-is,
such as:

cpp
// gets the version contained in the entity identifier
auto version = entt::to_version(entity);

Components are assigned to or removed from entities at any time.
The emplace member function template creates, initializes and assigns to an
entity the given component. It accepts a variable number of arguments to use to
construct the component itself:

cpp
registry.emplace<position>(entity, 0., 0.);

// ...

auto &vel = registry.emplace<velocity>(entity);
vel.dx = 0.;
vel.dy = 0.;

The default storage detects aggregate types internally and exploits aggregate
initialization when possible.
Therefore, it is not strictly necessary to define a constructor for each type.

The insert member function works with ranges and is used to:

  • Assign the same component to all entities at once when a type is specified as
    a template parameter or an instance is passed as an argument:

    cpp
    // default initialized type assigned by copy to all entities
    registry.insert<position>(first, last);
    
    // user-defined instance assigned by copy to all entities
    registry.insert(from, to, position{0., 0.});
  • Assign a set of components to the entities when a range is provided (the
    length of the range of components must be the same of that of entities):

    cpp
    // first and last specify the range of entities, instances points to the first element of the range of components
    registry.insert<position>(first, last, instances);

If an entity already has the given component, the replace and patch member
function templates are used to update it:

cpp
// replaces the component in-place
registry.patch<position>(entity, [](auto &pos) { pos.x = pos.y = 0.; });

// constructs a new instance from a list of arguments and replaces the component
registry.replace<position>(entity, 0., 0.);

When it is unknown whether an entity already owns an instance of a component,
emplace_or_replace is the function to use instead:

cpp
registry.emplace_or_replace<position>(entity, 0., 0.);

This is a slightly faster alternative to the following snippet:

cpp
if(registry.all_of<velocity>(entity)) {
    registry.replace<velocity>(entity, 0., 0.);
} else {
    registry.emplace<velocity>(entity, 0., 0.);
}

The all_of and any_of member functions may also be useful if in doubt about
whether or not an entity has all the components in a set or any of them:

cpp
// true if entity has all the given components
bool all = registry.all_of<position, velocity>(entity);

// true if entity has at least one of the given components
bool any = registry.any_of<position, velocity>(entity);

If the goal is to delete a component from an entity that owns it, the erase
member function template is the way to go:

cpp
registry.erase<position>(entity);

When in doubt whether the entity owns the component, use the remove member
function instead. It behaves similarly to erase but it drops the component if
and only if it exists, otherwise it returns safely to the caller:

cpp
registry.remove<position>(entity);

The clear member function works similarly and is used to either:

  • Erases all instances of the given components from the entities that own them:

    cpp
    registry.clear<position>();
  • Or destroy all entities in a registry at once:

    cpp
    registry.clear();

Finally, references to components are obtained simply as:

cpp
const auto &cregistry = registry;

// const and non-const reference
const auto &crenderable = cregistry.get<renderable>(entity);
auto &renderable = registry.get<renderable>(entity);

// const and non-const references
const auto [cpos, cvel] = cregistry.get<position, velocity>(entity);
auto [pos, vel] = registry.get<position, velocity>(entity);

If the existence of the component is not certain, try_get is the more suitable
function instead.

Observe changes

By default, each storage comes with a mixin that adds signal support to it.
This allows for fancy things like dependencies and reactive systems.

The on_construct member function returns a sink (which is an object for
connecting and disconnecting listeners) for those interested in notifications
when a new instance of a given component type is created:

cpp
// connects a free function
registry.on_construct<position>().connect<&my_free_function>();

// connects a member function
registry.on_construct<position>().connect<&my_class::member>(instance);

// disconnects a free function
registry.on_construct<position>().disconnect<&my_free_function>();

// disconnects a member function
registry.on_construct<position>().disconnect<&my_class::member>(instance);

Similarly, on_destroy and on_update are used to receive notifications about
the destruction and update of an instance, respectively.
Because of how C++ works, listeners attached to on_update are only invoked
following a call to replace, emplace_or_replace or patch.

Runtime pools are also supported by providing an identifier to the functions
above:

cpp
registry.on_construct<position>("other"_hs).connect<&my_free_function>();

Refer to the following sections for more information about runtime pools.
In all cases, the function type of a listener is equivalent to the following:

cpp
void(entt::registry &, entt::entity);

All listeners are provided with the registry that triggered the notification and
the involved entity. Note also that:

  • Listeners for construction signals are invoked after components have been
    created.

  • Listeners designed to observe changes are invoked after components have
    been updated.

  • Listeners for destruction signals are invoked before components have been
    destroyed.

There are also some limitations on what a listener can and cannot do:

  • Connecting and disconnecting other functions from within the body of a
    listener should be avoided. It can lead to undefined behavior in some cases.

  • Removing the component from within the body of a listener that observes the
    construction or update of instances of a given type is not allowed.

  • Assigning and removing components from within the body of a listener that
    observes the destruction of instances of a given type should be avoided. It
    can lead to undefined behavior in some cases. This type of listeners is
    intended to provide users with an easy way to perform cleanup and nothing
    more.

Please, refer to the documentation of the signal class to know about all the
features it offers.
There are many useful but less known functionalities that are not described
here, such as the connection objects or the possibility to attach listeners with
a list of parameters that is shorter than that of the signal itself.

Auto-binding

Users don't need to create bindings manually each and every time. For managed
types, they can have EnTT setup listeners automatically.
The library searches the types for functions with specific names and signatures,
as in the following example:

cpp
struct my_type {
    static void on_construct(entt::registry &registry, const entt::entity entt);
    static void on_update(entt::registry &registry, const entt::entity entt);
    static void on_destroy(entt::registry &registry, const entt::entity entt);

    // ...
};

As soon as a storage is created for such a defined type, these functions are
associated with the respective signals. The function name is self-explanatory of
the target signal.

Entity lifecycle

Observing entities is also possible. In this case, the user must use the entity
type instead of the component type:

cpp
registry.on_construct<entt::entity>().connect<&my_listener>();

Since entity storage is unique within a registry, if a name is provided it is
ignored and therefore discarded.
As for the function signature, this is exactly the same as the components.

Entities support all types of signals: construct, destroy, and update. The
latter is perhaps ambiguous as an entity is not truly updated. Rather, its
identifier is created and finally released.
Indeed, the update signal is meant to send general notifications regarding an
entity. It can be triggered via the patch function, as is the case with
components:

cpp
registry.patch<entt::entity>(entity);

Destroying an entity and then updating the version of an identifier does not
give rise to these types of signals under any circumstances instead.
Finally, note that listeners that observe the destruction of an entity are
invoked after all components have been removed, not before. This is
because the entity would be invalidated before deleting its elements otherwise,
making it difficult for the user to write component listeners.

Listeners disconnection

The destruction order of the storage classes and therefore the disconnection of
the listeners is completely random.
There are no guarantees today, and while the logic is easily discerned, it is
not guaranteed that it will remain so in the future.

For example, a listener getting disconnected after a component is discarded as a
result of pool destruction is most likely a recipe for problems.
Rather, it is advisable to invoke the clear function of the registry before
destroying it. This forces the deletion of all components and entities without
ever discarding the pools.
As a result, a listener that wants to access components, entities, or pools can
safely do so against a still valid registry, while checking for the existence of
the various elements as appropriate.

They call me reactive storage

Signals are the basic tools to construct reactive systems, even if they are not
enough on their own. EnTT tries to take another step in that direction with
its reactive mixin.
In order to explain what reactive systems are, this is a slightly revised quote
from the documentation of the library that first introduced this tool,
Entitas:

Imagine you have 100 fighting units on the battlefield, but only 10 of them
changed their positions. Instead of using a normal system and updating all 100
entities depending on the position, you can use a reactive system which will
only update the 10 changed units. So efficient.

In EnTT, this means iterating over a reduced set of entities and components
than what would otherwise be returned from a view or group.
On these words, however, the similarities with the proposal of Entitas also
end. The rules of the language and the design of the library obviously impose
and allow different things.

A reactive mixin can be used on a standalone storage with any value type
(perhaps using an alias to simplify its use):

cpp
using reactive_storage = entt::reactive_mixin<entt::storage<void>>;

entt::registry registry{};
reactive_storage storage{};

storage.bind(registry);

In this case, it must be provided with a reference registry for subsequent
operations.
Alternatively, when using the value type provided by EnTT, it is also possible
to create a reactive storage directly inside a registry:

cpp
entt::registry registry{};
auto &storage = registry.storage<entt::reactive>("observer"_hs);

In the latter case, there is the advantage that, in the event of destruction of
an entity, this storage is also automatically cleaned up.
Also note that, unlike all other storage, these classes do not support signals
by default (although they can be enabled if necessary).

Once it has been created and associated with a registry, the reactive mixin
needs to be informed about what it should observe.
Here the choice boils down to three main events affecting all elements (entities
or components), namely creation, update or destruction:

cpp
storage
    // observe position component construction
    .on_construct<position>()
    // observe velocity component update
    .on_update<velocity>()
    // observe renderable component destruction
    .on_destroy<renderable>();

It goes without saying that it is possible to observe multiple events of the
same type or of different types with the same storage.
For example, to know which entities have been assigned or updated a component of
a certain type:

cpp
storage
    .on_construct<my_type>()
    .on_update<my_type>();

Note that all configurations are in or and never in and. Therefore, to track
entities that have been assigned two different components, there are a couple of
options:

  • Create two reactive storage, then combine them in a view:

    cpp
    first_storage.on_construct<position>();
    second_storage.on_construct<velocity>();
    
    for(auto entity: entt::basic_view{first_storage, second_storage}) {
        // ...
    }
  • Use a reactive storage with a non-void value type and a custom tracking
    function for the purpose:

    cpp
    using my_reactive_storage = entt::reactive_mixin<entt::storage<bool>>;
    
    void callback(my_reactive_storage &storage, const entt::registry &, const entt::entity entity) {
        storage.contains(entity) ? (storage.get(entity) = true) : storage.emplace(entity, false);
    }
    
    // ...
    
    my_reactive_storage storage{};
    storage
        .on_construct<position, &callback>()
        .on_construct<velocity, &callback>();
    
    // ...
    
    for(auto [entity, both_were_added]: storage.each()) {
        if(both_were_added) {
            // ...
        }
    }

As highlighted in the last example, the reactive mixin tracks the entities that
match the given conditions and saves them aside. However, this behavior can be
changed.
For example, it is possible to capture all and only the entities for which a
certain component has been updated but only if a specific value is within a
given range:

cpp
void callback(reactive_storage &storage, const entt::registry &registry, const entt::entity entity) {
    storage.remove(entity);

    if(const auto x = registry.get<position>(entity).x; x >= min_x && x <= max_x) {
        storage.emplace(entity);
    }
}

// ...

storage.on_update<position, &callback>();

This makes reactive storage extremely flexible and usable in a large number of
cases.
Finally, once the entities of interest have been collected, it is possible to
visit the storage like any other:

cpp
for(auto entity: storage) {
    // ...
}

Wrapping it in a view and combining it with other views is another option:

cpp
for(auto [entity, pos]: (entt:.basic_view{storage} | registry.view<position>(entt::exclude<velocity>)).each()) {
    // ...
}

In order to simplify this last use case, the reactive mixin also provides a
specific function that returns a view of the storage already filtered according
to the provided requirements:

cpp
for(auto [entity, pos]: storage.view<position>(entt::exclude<velocity>).each()) {
    // ...
}

The registry used in this case is the one associated with the storage and also
available via the registry function.

It should be noted that a reactive storage never deletes its entities (and
elements, if any). To process and then discard entities at regular intervals,
refer to the clear function available by default for each storage type.
Similarly, the reactive mixin does not disconnect itself from observed storages
upon destruction. Therefore, users have to do this themselves:

cpp
entt::registry = storage.registry();

registry.on_construct<position>().disconnect(&storage);
registry.on_construct<velocity>().disconnect(&storage);

Destroying a reactive storage without disconnecting it from observed pools will
result in undefined behavior.

Sorting: is it possible?

Sorting entities and components is possible using an in-place algorithm that
does not require memory allocations and is therefore quite convenient.
There are two functions that respond to slightly different needs:

  • Components are sorted either directly:

    cpp
    registry.sort<renderable>([](const renderable &lhs, const renderable &rhs) {
        return lhs.z < rhs.z;
    });

    Or by accessing their entities:

    cpp
    registry.sort<renderable>([](const entt::entity lhs, const entt::entity rhs) {
        return entt::registry::entity(lhs) < entt::registry::entity(rhs);
    });

    There exists also the possibility to use a custom sort function object for
    when the usage pattern is known.

  • Components are sorted according to the order imposed by another component:

    cpp
    registry.sort<movement, physics>();

    In this case, instances of movement are arranged in memory so that cache
    misses are minimized when the two components are iterated together.

As a side note, the use of groups limits the possibility of sorting pools of
components. Refer to the specific documentation for more details.

Helpers

The so-called helpers are small classes and functions mainly designed to offer
built-in support for the most basic functionalities.

Null entity

The entt::null variable models the concept of a null entity.
The library guarantees that the following expression always returns false:

cpp
registry.valid(entt::null);

A registry rejects the null entity in all cases because it is not considered
valid. It also means that the null entity cannot own components.
The type of the null entity is internal and should not be used for any purpose
other than defining the null entity itself. However, there exist implicit
conversions from the null entity to identifiers of any allowed type:

cpp
entt::entity null = entt::null;

Similarly, the null entity compares to any other identifier:

cpp
const auto entity = registry.create();
const bool null = (entity == entt::null);

As for its integral form, the null entity only affects the entity part of an
identifier and is instead completely transparent to its version.

Be aware that entt::null and entity 0 are different. Likewise, a zero
initialized entity is not the same as entt::null. Therefore, although
entt::entity{} is in some sense an alias for entity 0, none of them are used
to create a null entity.

Tombstone

Similar to the null entity, the entt::tombstone variable models the concept of
a tombstone.
Once created, the integral form of the two values is the same, although they
affect different parts of an identifier. In fact, the tombstone only uses the
version part of it and is completely transparent to the entity part.

Also in this case, the following expression always returns false:

cpp
registry.valid(entt::tombstone);

Moreover, users cannot set the tombstone version when releasing an entity:

cpp
registry.destroy(entity, entt::tombstone);

In this case, a different version number is implicitly generated.
The type of a tombstone is internal and can change at any time. However, there
exist implicit conversions from a tombstone to identifiers of any allowed type:

cpp
entt::entity null = entt::tombstone;

Similarly, the tombstone compares to any other identifier:

cpp
const auto entity = registry.create();
const bool tombstone = (entity == entt::tombstone);

Be aware that entt::tombstone and entity 0 are different. Likewise, a zero
initialized entity is not the same as entt::tombstone. Therefore, although
entt::entity{} is in some sense an alias for entity 0, none of them are used
to create tombstones.

To entity

This function accepts a storage and an instance of a component of the storage
type, then it returns the entity associated with the latter:

cpp
const auto entity = entt::to_entity(registry.storage<position>(), instance);

Where instance is a component of type position. A null entity is returned in
case the instance does not belong to the registry.

Dependencies

The registry class is designed to create short circuits between its member
functions. This greatly simplifies the definition of a dependency.
For example, the following adds (or replaces) the component a_type whenever
my_type is assigned to an entity:

cpp
registry.on_construct<my_type>().connect<&entt::registry::emplace_or_replace<a_type>>();

Similarly, the code below removes a_type from an entity whenever my_type is
assigned to it:

cpp
registry.on_construct<my_type>().connect<&entt::registry::remove<a_type>>();

A dependency is easily broken as follows:

cpp
registry.on_construct<my_type>().disconnect<&entt::registry::emplace_or_replace<a_type>>();

There are many other types of dependencies. In general, most of the functions
that accept an entity as their first argument are good candidates for this
purpose.

Invoke

The invoke helper allows to propagate a signal to a member function of a
component without having to extend it:

cpp
registry.on_construct<clazz>().connect<entt::invoke<&clazz::func>>();

All it does is pick up the right component for the received entity and invoke
the requested method, passing on the arguments if necessary.

Connection helper

Connecting signals can quickly become cumbersome.
This utility aims to simplify the process by grouping the calls:

cpp
entt::sigh_helper{registry}
    .with<position>()
        .on_construct<&a_listener>()
        .on_destroy<&another_listener>()
    .with<velocity>("other"_hs)
        .on_update<yet_another_listener>();

Runtime pools are also supported by providing an identifier when calling with,
as shown in the previous snippet. Refer to the following sections for more
information about runtime pools.
Obviously, this helper does not make the code disappear but it should at least
reduce the boilerplate in the most complex cases.

Handle

A handle is a thin wrapper around an entity and a registry. It replicates the
API of a registry by offering functions such as get or emplace. The
difference being that the entity is implicitly passed to the registry.
It is default constructible as an invalid handle that contains a null registry
and a null entity. When it contains a null registry, calling functions that
delegate execution to the registry causes undefined behavior. It is recommended
to test for validity with its implicit cast to bool if in doubt.
A handle is also non-owning, meaning that it is freely copied and moved around
without affecting its entity (in fact, handles happen to be trivially copyable).
An implication of this is that mutability becomes part of the type.

There are two aliases that use entt::entity as their default entity:
entt::handle and entt::const_handle.
Users can also easily create their own aliases for custom identifiers as:

cpp
using my_handle = entt::basic_handle<entt::basic_registry<my_identifier>>;
using my_const_handle = entt::basic_handle<const entt::basic_registry<my_identifier>>;

Non-const handles are also implicitly convertible to const handles out of the
box but not the other way around.

This class is intended to simplify function signatures. In case of functions
that take a registry and an entity and do most of their work on that entity,
users might want to consider using handles, either const or non-const.

Organizer

The organizer class template offers support for creating an execution graph
from a set of functions and their requirements on resources.
The resulting tasks are not executed in any case. This is not the goal of this
tool. Instead, they are returned to the user in the form of a graph that allows
for safe execution.

All functions are added in order of execution to the organizer:

cpp
entt::organizer organizer;

// adds a free function to the organizer
organizer.emplace<&free_function>();

// adds a member function and an instance on which to invoke it to the organizer
clazz instance;
organizer.emplace<&clazz::member_function>(&instance);

// adds a decayed lambda directly
organizer.emplace(+[](const void *, entt::registry &) { /* ... */ });

These are the parameters that a free function or a member function can accept:

  • A possibly constant reference to a registry.
  • An entt::basic_view with any possible combination of storage classes.
  • A possibly constant reference to any type T (that is, a context variable).

The function type for free functions and decayed lambdas passed as parameters to
emplace is void(const void *, entt::registry &) instead. The first parameter
is an optional pointer to user defined data to provide upon registration:

cpp
clazz instance;
organizer.emplace(+[](const void *, entt::registry &) { /* ... */ }, &instance);

In all cases, it is also possible to associate a name with the task when
creating it. For example:

cpp
organizer.emplace<&free_function>("func");

When a function is registered with the organizer, everything it accesses is
considered a resource (views are unpacked and their types are treated as
resources). The constness of a type also dictates its access mode (RO/RW). In
turn, this affects the resulting graph, since it influences the possibility of
launching tasks in parallel.
As for the registry, if a function does not explicitly request it or requires a
constant reference to it, it is considered a read-only access. Otherwise, it is
considered as read-write access. All functions have the registry among their
resources.

When registering a function, users can also require resources that are not in
the list of parameters of the function itself. These are declared as template
parameters:

cpp
organizer.emplace<&free_function, position, velocity>("func");

Similarly, users can override the access mode of a type again via template
parameters:

cpp
organizer.emplace<&free_function, const renderable>("func");

In this case, even if renderable appears among the parameters of the function
as not constant, it is treated as constant as regards the generation of the task
graph.

To generate the task graph, the organizer offers the graph member function:

cpp
std::vector<entt::organizer::vertex> graph = organizer.graph();

A graph is returned in the form of an adjacency list. Each vertex offers the
following features:

  • ro_count and rw_count: the number of resources accessed in read-only or
    read-write mode.

  • ro_dependency and rw_dependency: type info objects associated with the
    parameters of the underlying function.

  • top_level: true if a node is a top level one (it has no entering edges),
    false otherwise.

  • info: type info object associated with the underlying function.

  • name: the name associated with the given vertex if any, a null pointer
    otherwise.

  • callback: a pointer to the function to execute and whose function type is
    void(const void *, entt::registry &).

  • data: optional data to provide to the callback.

  • children: the vertices reachable from the given node, in the form of indices
    within the adjacency list.

Since the creation of pools and resources within the registry is not necessarily
thread safe, each vertex also offers a prepare function which is used to setup
a registry for execution with the created graph:

cpp
auto graph = organizer.graph();
entt::registry registry;

for(auto &&node: graph) {
    node.prepare(registry);
}

The actual scheduling of the tasks is the responsibility of the user, who can
use the preferred tool.

Context variables

Each registry has a context associated with it, which is an any object map
accessible by both type and name for convenience. The name is not really a
name though. In fact, it is a numeric id of type id_type used as a key for the
variable. Any value is accepted, even runtime ones.
The context is returned via the ctx functions and offers a minimal set of
features including the following:

cpp
// creates a new context variable by type and returns it
registry.ctx().emplace<my_type>(42, 'c');

// creates a new named context variable by type and returns it
registry.ctx().emplace_as<my_type>("my_variable"_hs, 42, 'c');

// inserts or assigns a context variable by (deduced) type and returns it
registry.ctx().insert_or_assign(my_type{42, 'c'});

// inserts or assigns a named context variable by (deduced) type and returns it
registry.ctx().insert_or_assign("my_variable"_hs, my_type{42, 'c'});

// gets the context variable by type as a non-const reference from a non-const registry
auto &var = registry.ctx().get<my_type>();

// gets the context variable by name as a const reference from either a const or a non-const registry
const auto &cvar = registry.ctx().get<const my_type>("my_variable"_hs);

// resets the context variable by type
registry.ctx().erase<my_type>();

// resets the context variable associated with the given name
registry.ctx().erase<my_type>("my_variable"_hs);

There are no strict requirements on the type of a context variable, such as that
it must be constructible or movable by default. However, if the supplied type
does not match that of the variable when using a name, the operation
fails.
For all users who want to use the context but do not want to create elements,
the contains and find functions are also available:

cpp
const bool contains = registry.ctx().contains<my_type>();
const my_type *value = registry.ctx().find<const my_type>("my_variable"_hs);

Also in this case, both functions support constant types and accept a name for
the variable to look up, as does at.

Aliased properties

A context also supports creating aliases for existing variables that are not
directly managed by the registry. Const and therefore read-only variables are
also accepted.
To do that, the type used upon construction must be a reference type and an
lvalue is necessarily provided as an argument:

cpp
time clock;
registry.ctx().emplace<time &>(clock);

Read-only aliased properties are created using const types instead:

cpp
registry.ctx().emplace<const time &>(clock);

Note that insert_or_assign does not support aliased properties and users must
necessarily use emplace or emplace_as for this purpose.
When insert_or_assign is used to update an aliased property, it converts
the property itself into a non-aliased one.

From the point of view of the user, there are no differences between a variable
that is managed by the registry and an aliased property. However, read-only
variables are not accessible as non-const references:

cpp
// read-only variables only support const access
const my_type *ptr = registry.ctx().find<const my_type>();
const my_type &var = registry.ctx().get<const my_type>();

Aliased properties are erased as it happens with any other variable. Similarly,
it is also possible to assign them a name.

Snapshot: complete vs continuous

This module comes with bare minimum support for serialization.
It does not convert components to bytes directly, there was not the need of
another tool for serialization out there. Instead, it accepts an opaque object
with a suitable interface (namely an archive) to serialize its internal data
structures and restore them later. The way types and instances are converted to
a bunch of bytes is completely in charge to the archive and thus to final users.

The goal of the serialization part is to allow users to make both a dump of the
entire registry or a narrower snapshot, that is to select only the components in
which they are interested.
Intuitively, the use cases are different. As an example, the first approach is
suitable for local save/restore functionalities while the latter is suitable for
creating client-server applications and for transferring somehow parts of the
representation side to side.

To take a snapshot of a registry, use the snapshot class:

cpp
output_archive output;

entt::snapshot{registry}
    .get<entt::entity>(output)
    .get<a_component>(output)
    .get<another_component>(output);

It is not necessary to invoke all functions each and every time. What functions
to use in which case mostly depends on the goal.

When getting an entity type, the snapshot class serializes all entities along
with their versions.
In all other cases, entities and components from a given storage are passed to
the archive. Named pools are also supported:

cpp
entt::snapshot{registry}.get<a_component>(output, "other"_hs);

There exists another version of the get member function that accepts a range
of entities to serialize. It can be used to filter out those entities that
should not be serialized for some reasons:

cpp
const auto view = registry.view<serialize>();
output_archive output;

entt::snapshot{registry}
    .get<a_component>(output, view.begin(), view.end())
    .get<another_component>(output, view.begin(), view.end());

Once a snapshot is created, there exist mainly two ways to load it: as a whole
and in a kind of continuous mode.
The following sections describe both loaders and archives in details.

Snapshot loader

A snapshot loader requires that the destination registry be empty. It loads all
the data at once while keeping intact the identifiers that the entities
originally had:

cpp
input_archive input;

entt::snapshot_loader{registry}
    .get<entt::entity>(input)
    .get<a_component>(input)
    .get<another_component>(input)
    .orphans();

It is not necessary to invoke all functions each and every time. What functions
to use in which case mostly depends on the goal.
For obvious reasons, what is important is that the data are restored in exactly
the same order in which they were serialized.

When getting an entity type, a snapshot loader restores all entities with the
versions that they originally had at the source.
In all other cases, entities and components are restored in a given storage. If
the registry does not contain the entity, it is also created accordingly. As for
the snapshot class, named pools are supported too:

cpp
entt::snapshot_loader{registry}.get<a_component>(input, "other"_hs);

Finally, the orphans member function releases the entities that have no
components after a restore, if any.

Continuous loader

A continuous loader is designed to load data from a source registry to a
(possibly) non-empty destination. The loader accommodates in a registry more
than one snapshot in a sort of continuous loading that updates the destination
one step at a time.
Identifiers that entities originally had are not transferred to the target.
Instead, the loader maps remote identifiers to local ones while restoring a
snapshot. Wrapping the archive is a conveninent way of updating identifiers that
are part of components automatically (see the example below).
Another difference with the snapshot loader is that the continuous loader has an
internal state that must persist over time. Therefore, there is no reason to
limit its lifetime to that of a temporary object:

cpp
entt::continuous_loader loader{registry};
input_archive input;

auto archive = [&loader, &input](auto &value) {
    input(value);

    if constexpr(std::is_same_v<std::remove_reference_t<decltype(value)>, dirty_component>) {
        value.parent = loader.map(value.parent);
        value.child = loader.map(value.child);
    }
};

loader
    .get<entt::entity>(input)
    .get<a_component>(input)
    .get<another_component>(input)
    .get<dirty_component>(input)
    .orphans();

It is not necessary to invoke all functions each and every time. What functions
to use in which case mostly depends on the goal.
For obvious reasons, what is important is that the data are restored in exactly
the same order in which they were serialized.

When getting an entity type, a loader restores groups of entities and maps
each entity to a local counterpart when required. For each remote identifier not
yet registered by the loader, a local identifier is created so as to keep the
local entity in sync with the remote one.
In all other cases, entities and components are restored in a given storage. If
the registry does not contain the entity, it is also tracked accordingly. As for
the snapshot class, named pools are supported too:

cpp
loader.get<a_component>(input, "other"_hs);

Finally, the orphans member function releases the entities that have no
components after a restore, if any.

Archives

Archives must publicly expose a predefined set of member functions. The API is
straightforward and consists only of a group of function call operators that
are invoked by the snapshot class and the loaders.

In particular:

  • An output archive (the one used when creating a snapshot) exposes a function
    call operator with the following signature to store entities:

    cpp
    void operator()(entt::entity);

    Where entt::entity is the type of the entities used by the registry.
    Note that all member functions of the snapshot class also make an initial call
    to store aside the size of the set they are going to store. In this case,
    the expected function type for the function call operator is:

    cpp
    void operator()(std::underlying_type_t<entt::entity>);

    In addition, an archive accepts (const) references to the types of component
    to serialize. Therefore, given a type T, the archive offers a function call
    operator with the following signature:

    cpp
    void operator()(const T &);

    The output archive can freely decide how to serialize the data. The registry
    is not affected at all by the decision.

  • An input archive (the one used when restoring a snapshot) exposes a function
    call operator with the following signature to load entities:

    cpp
    void operator()(entt::entity &);

    Where entt::entity is the type of the entities used by the registry. Each
    time the function is invoked, the archive reads the next element from the
    underlying storage and copies it in the given variable.
    All member functions of a loader class also make an initial call to read the
    size of the set they are going to load. In this case, the expected function
    type for the function call operator is:

    cpp
    void operator()(std::underlying_type_t<entt::entity> &);

    In addition, an archive accepts references to the types of component to
    restore. Therefore, given a type T, the archive contains a function call
    operator with the following signature:

    cpp
    void operator()(T &);

    Every time this operator is invoked, the archive reads the next element from
    the underlying storage and copies it in the given variable.

One example to rule them all

EnTT comes with some examples (actually some tests) that show how to integrate
a well known library for serialization as an archive. It uses
Cereal C++ under the hood, mainly
because I wanted to learn how it works at the time I was writing the code.

The code is not production-ready and it is not neither the only nor
(probably) the best way to do it. However, feel free to use it at your own
risk.
The basic idea is to store everything in a group of queues in memory, then bring
everything back to the registry with different loaders.

Storage

Pools of components are specialized versions of the sparse set class. Each
pool contains all the instances of a single component type and all the entities
to which it is assigned.
Sparse arrays are paged to avoid wasting memory. Packed arrays of components
are also paged to have pointer stability upon additions. Packed arrays of
entities are not instead.
All pools rearranges their items in order to keep the internal arrays tightly
packed and maximize performance, unless full pointer stability is enabled.

Component traits

In EnTT, almost everything is customizable. Pools are no exception.
In this case, the standardized way to access all component properties is the
component_traits class.

Various parts of the library access component properties through this class. It
makes it possible to use any type as a component, as long as its specialization
of component_traits implements all the required functionalities.
The non-specialized version of this class contains the following members:

  • in_place_delete: Type::in_place_delete if present, true for non-movable
    types and false otherwise.

  • page_size: Type::page_size if present, ENTT_PACKED_PAGE for non-empty
    types and 0 otherwise.

Where Type is any type of component. Properties are customized by specializing
the above class and defining its members, or by adding only those of interest to
a component definition:

cpp
struct transform {
    static constexpr auto in_place_delete = true;
    // ... other data members ...
};

The component_traits class template takes care of extracting the properties
from the supplied type.
Plus, it can be specialized and constrained with a concept to further customize
it on a per type or per feature basis.

Empty type optimization

An empty type T is such that std::is_empty_v<T> returns true. They also are
the same types for which empty base optimization (EBO) is possible.
EnTT handles these types in a special way, optimizing both in terms of
performance and memory usage. However, this also has consequences that are worth
mentioning.

When an empty type is detected, it is not instantiated by default. Therefore,
only the entities to which it is assigned are made available. There does not
exist a way to get empty types from a storage or a registry. Views and groups
never return their instances too (for example, during a call to each).
On the other hand, iterations are faster because only the entities to which the
type is assigned are considered. Moreover, less memory is used, mainly because
there does not exist any instance of the component, no matter how many entities
it is assigned to.

More in general, none of the feature offered by the library is affected, but for
the ones that require to return actual instances.
This optimization is disabled by defining the ENTT_NO_ETO macro. In this case,
empty types are treated like all other types. Setting a page size at component
level via the component_traits class template is another way to disable this
optimization selectively rather than globally.

Void storage

A void storage (entt::storage<void> or entt::basic_storage<void, Entity>),
is a fully functional storage type used to create pools not associated with a
particular component type.
From a technical point of view, it is in all respects similar to a storage for
empty types when their optimization is enabled. Pagination is disabled as well
as pointer stability (as not necessary).
However, this should be preferred to using a simple sparse set. In particular,
a void storage offers all those feature normally offered by other storage types.
Therefore, it is a perfectly valid pool for use with views and groups or within
a registry.

Entity storage

This storage is such that the component type is the same as the entity type, for
example entt::storage<entt::entity> or entt::basic_storage<Type, Type>.
For this type of pools, there is a specific specialization within EnTT. In
fact, entities are subject to different rules with respect to components
(although still customizable by the user if needed). In particular:

  • Entities are never truly deleted. They are moved out of the list of entities
    in use and their versions are updated automatically.

  • There are no emplace or insert functions in its interface. Instead, a
    range of generate functions are provided for creating or recycling entities.

  • The each function returns an iterable object to visit the entities in use,
    that is, those not marked as ready for reuse. To iterate all the entities it
    is necessary to iterate the underlying sparse set instead.

This kind of storage is designed to be used where any other storage is fine and
can therefore be combined with views, groups and so on.

Reserved identifiers

Since the entity storage is the one in charge of generating identifiers, it is
also possible to request that some of them be reserved and never returned.
By doing so, users can then generate and manage them autonomously, as needed.

To set a starting identifier, the start_from function is invoked as follows:

cpp
storage.start_from(entt::entity{100});

Note that the version is irrelevant and is ignored in all cases. Identifiers are
always generated with default version.
By calling start_from as above, the first 100 elements are discarded and the
first identifier returned is the one with entity 100 and version 0.

One of a kind to the registry

Within the registry, an entity storage is treated in all respects like any other
storage.
Therefore, it is possible to add mixins to it as well as retrieve it via the
storage function. It can also be used as storage in a view (for exclude-only
views for example):

cpp
auto view = registry.view<entt::entity>(entt::exclude<my_type>);

However, it is also subject to a couple of exceptions, partly out of necessity
and partly for ease of use.

In particular, it is not possible to create multiple elements of this type.
This means that the name used to retrieve this kind of storage is ignored and
the registry will only ever return the same element to the caller. For example:

cpp
auto &other = registry.storage<entt::entity>("other"_hs);

In this case, the identifier is discarded as is. The call is in all respects
equivalent to the following:

cpp
auto &storage = registry.storage<entt::entity>();

Because entity storage does not have a name, it cannot be retrieved via the
opaque storage function either.
It would make no sense to try anyway, given that the type of the registry and
therefore its entity type are known regardless.

Finally, when the user asks the registry for an iterable object to visit all the
storage elements inside it as follows:

cpp
for(auto [id, storage]: registry.storage()) {
    // ...
}

Entity storage is never returned. This simplifies many tasks (such as copying an
entity) and fits perfectly with the fact that this type of storage does not have
an identifier inside the registry.

Pointer stability

The ability to achieve pointer stability for one, several or all components is a
direct consequence of the design of EnTT and of its default storage.
In fact, although it contains what is commonly referred to as a packed array,
the default storage is paged and does not suffer from invalidation of references
when it runs out of space and has to reallocate.
However, this is not enough to ensure pointer stability in case of deletion. For
this reason, a stable deletion method is also offered. This one is such that
the position of the elements is preserved by creating tombstones upon deletion
rather than trying to fill the holes that are created.

For performance reasons, EnTT favors storage compaction in all cases, although
often accessing a component occurs mostly randomly or traversing pools in a
non-linear order on the user side (as in the case of a hierarchy).
In other words, pointer stability is not automatic but is enabled on request.

In-place delete

The library offers out of the box support for in-place deletion, thus offering
storage with completely stable pointers. This is achieved by specializing the
component_traits class or by adding the required properties to the component
definition when needed.
Views and groups adapt accordingly when they detect a storage with a different
deletion policy than the default. In particular:

  • Groups are incompatible with stable storage and even refuse to compile.
  • Multi type and runtime views are completely transparent to storage policies.
  • Single type views for stable storage types offer the same interface of multi
    type views. For example, only size_hint is available.

In other words, the more generic version of a view is provided in case of stable
storage, even for a single type view.
In no case a tombstone is returned from the view itself. Likewise, non-existent
components are not returned, which could otherwise result in an UB.

Hierarchies and the like

EnTT does not attempt in any way to offer built-in methods with hidden or
unclear costs to facilitate the creation of hierarchies.
There are various solutions to the problem, such as using the following class:

cpp
struct relationship {
    std::size_t children{};
    entt::entity first{entt::null};
    entt::entity prev{entt::null};
    entt::entity next{entt::null};
    entt::entity parent{entt::null};
    // ... other data members ...
};

However, it should be pointed out that the possibility of having stable pointers
for one, many or all types solves the problem of hierarchies at the root in many
cases.
In fact, if a certain type of component is visited mainly in random order or
according to hierarchical relationships, using direct pointers has many
advantages:

cpp
struct transform {
    static constexpr auto in_place_delete = true;

    transform *parent;
    // ... other data members ...
};

Furthermore, it is quite common for a group of elements to be created close in
time and therefore fallback into adjacent positions, thus favoring locality even
on random accesses. Locality that is not sacrificed over time given the
stability of storage positions, with undoubted performance advantages.

Meet the runtime

EnTT takes advantage of what the language offers at compile-time. However,
this can have its downsides (well known to those familiar with type erasure
techniques).
To fill the gap, the library also provides a bunch of utilities and features
that are invaluable to handle types and pools at runtime.

A base class to rule them all

Storage classes are fully self-contained types. They are extended via mixins
to add more functionalities (generic or type specific). In addition, they offer
a basic set of functions that already allow users to go very far.
The aim is to limit the need for customizations as much as possible, offering
what is usually necessary for the vast majority of cases.

When a storage is used through its base class (for example, when its actual type
is not known), there is always the possibility of receiving a type_info object
for the type of elements associated with the entities (if any):

cpp
if(entt::type_id<velocity>() == base.info()) {
    // ...
}

Furthermore, all features rely on internal functions that forward the calls to
the mixins. The latter can then make use of any information, which is set via
bind:

cpp
base.bind(registry);

The bind function accepts any element by reference or by value and forwards it
to derived classes.
This is how a registry passes itself to all pools that support signals and
also why a storage keeps sending events without requiring the registry to be
passed to it every time.

Alongside these more specific things, there are also a couple of functions
designed to address some common requirements such as copying an entity.
In particular, the base class behind a storage offers the possibility to take
the value associated with an entity through an opaque pointer:

cpp
const void *instance = base.value(entity);

Similarly, the non-specialized push function accepts an optional opaque
pointer and behaves differently depending on the case:

  • When the pointer is null, the function tries to default-construct an instance
    of the object to bind to the entity and returns true on success.

  • When the pointer is not null, the function tries to copy-construct an instance
    of the object to bind to the entity and returns true on success.

This means that, starting from a reference to the base, it is possible to bind
components with entities without knowing their actual type and even initialize
them by copy if needed:

cpp
// create a copy of an entity component by component
for(auto &&curr: registry.storage()) {
    if(auto &storage = curr.second; storage.contains(src)) {
        storage.push(dst, storage.value(src));
    }
}

This is particularly useful to clone entities in an opaque way. In addition, the
decoupling of features allows for filtering or use of different copying policies
depending on the type.

Beam me up, registry

EnTT allows the user to assign a name (or rather, a numeric identifier) to a
type and then create multiple pools of the same type:

cpp
using namespace entt::literals;
auto &&storage = registry.storage<velocity>("second pool"_hs);

If a name is not provided, the default storage associated with the given type is
always returned.
Since the storage are also self-contained, the registry does not duplicate its
own API for them. However, there is still no limit to the possibilities of use:

cpp
auto &&other = registry.storage<velocity>("other"_hs);

registry.emplace<velocity>(entity);
other.push(entity);

Anything that can be done via the registry interface can also be done directly
on the reference storage.
On the other hand, those calls involving all storage are guaranteed to also
reach manually created ones:

cpp
// removes the entity from both storage
registry.destroy(entity);

Finally, a storage of this type works with any view (which also accepts multiple
storages of the same type, if necessary):

cpp
// direct initialization
entt::basic_view direct{
    registry.storage<velocity>(),
    registry.storage<velocity>("other"_hs)
};

// concatenation
auto join = registry.view<velocity>() | entt::basic_view{registry.storage<velocity>("other"_hs)};

The possibility of direct use of storage combined with the freedom of being able
to create and use more than one of the same type opens the door to the use of
EnTT at runtime, which was previously quite limited.

Views and Groups

Views are a non-intrusive tool for working with entities and components without
affecting other functionalities or increasing memory consumption.
Groups are an intrusive tool to use to improve performance along critical paths
but which also has a price to pay for that.

There are mainly two kinds of views: compile-time (also known as view) and
runtime (also known as runtime_view).
The former requires a compile-time list of component (or storage) types and can
make several optimizations because of that. The latter is constructed at runtime
using numerical type identifiers instead and is a bit slower to iterate.
In both cases, creating and destroying views is not expensive at all since they
do not have any type of initialization.

Groups come in three different flavors: full-owning groups, partial-owning
groups
and non-owning groups. The main difference between them is in terms of
performance.
Groups can literally own one or more component types. They are allowed to
rearrange pools so as to speed up iterations. Roughly speaking: the more
components a group owns, the faster it is to iterate them.

Views

Single type views and multi type views behave differently and also have slightly
different APIs.

Single type views are specialized to give a performance boost in all cases.
There is nothing as fast as a single type view. They just walk through packed
(actually paged) arrays of elements and return them directly.
This kind of views also allows getting the exact number of elements they are
going to return.
Refer to the inline documentation for all the details.

Multi type views iterate entities that have at least all the given components.
During construction, they look at the number of elements available in each pool
and use the smallest set in order to speed up iterations.
This kind of views only allow to get the estimated number of elements they are
going to return.
Refer to the inline documentation for all the details.

Storing aside views is not required as they are extremely cheap to construct. In
fact, this is even discouraged when creating a view from a const registry. Since
all storage are lazily initialized, they may not exist when the view is created.
Thus, while perfectly usable, the view may contain pending references that are
never reinitialized with the actual storage.

Views share the way they are created by means of a registry:

cpp
// single type view
auto single = registry.view<position>();

// multi type view
auto multi = registry.view<position, velocity>();

Filtering entities by components is also supported:

cpp
auto view = registry.view<position, velocity>(entt::exclude<renderable>);

To iterate a view, either use it in a range-for loop:

cpp
auto view = registry.view<position, velocity, renderable>();

for(auto entity: view) {
    // a component at a time ...
    auto &position = view.get<position>(entity);
    auto &velocity = view.get<velocity>(entity);

    // ... multiple components ...
    auto [pos, vel] = view.get<position, velocity>(entity);

    // ... all components at once
    auto [pos, vel, rend] = view.get(entity);

    // ...
}

Or rely on the each member functions to iterate both entities and components
at once:

cpp
// through a callback
registry.view<position, velocity>().each([](auto entity, auto &pos, auto &vel) {
    // ...
});

// using an input iterator
for(auto &&[entity, pos, vel]: registry.view<position, velocity>().each()) {
    // ...
}

Note that entities can also be excluded from the parameter list when received
through a callback and this can improve even further the performance during
iterations.
Since they are not explicitly instantiated, empty components are not returned in
any case.

As a side note, in the case of single type views, get accepts but does not
strictly require a template parameter, since the type is implicitly defined.
However, when the type is not specified, the instance is returned using a tuple
for consistency with multi type views:

cpp
auto view = registry.view<const renderable>();

for(auto entity: view) {
    auto [renderable] = view.get(entity);
    // ...
}

Note: prefer the get member function of a view instead of that of a
registry during iterations to get the types iterated by the view itself.

Create once, reuse many times

Views support lazy initialization as well as storage swapping.
An empty (or partially initialized) view is such that it returns false when
converted to bool (to let the user know that it is not fully initialized) but it
also works as-is like any other view.

In order to initialize a view one piece at a time, it allows users to inject
storage classes when available:

cpp
entt::storage_for_t<velocity> storage{};
entt::view<entt::get_t<position, velocity>> view{};

view.storage(storage);

If there are multiple storages of the same type, it is possible to disambiguate
using the index of the element to be replaced:

cpp
view.storage<1>(storage);

The ability to literally replace a storage in a view also opens up its reuse
with different sets of entities.
For example, to filter a view based on two groups of entities with different
characteristics, there will be no need to reinitialize anything:

cpp
entt::view<entt::get<my_type, void>> view{registry.storage<my_type>>()};

entt::storage_for_t<void> the_good{};
entt::storage_for_t<void> the_bad{};

// initialize the sets above as needed

view.storage(the_good);

for(auto [entt, elem]: view) {
  // the good entities with their components here
}

view.storage(the_bad);

for(auto [entt, elem]: view) {
  // the bad entities with their components here
}

Finally, it should be noted that the lack of a storage is treated to all intents
and purposes as if it were an empty element.
Thus, a get storage (as in entt::get_t) makes the view empty automatically
while an exclude storage (as in entt::exclude_t) is ignored as if that part
of the filter did not exist.

Exclude-only

Exclude-only views are not really a thing in EnTT.
However, the same result can be achieved by combining the right storage into a
simple view.

If one gets to the root of the problem, the purpose of an exclude-only view is
to return entities that do not meet certain requirements.
Since entity storage, unlike exclude-only views, is a thing in EnTT, users
can leverage it for these kinds of queries. It is also guaranteed to be unique
within a registry and is always accessible when creating a view:

cpp
auto view = registry.view<entt::entity>(entt::exclude<my_type>);

The returned view is such that it will return only the entities that do not have
the my_type component, regardless of what other components they have.

View pack

Views are combined with storage objects and with each other to create new, more
specific queries.
The type returned when combining multiple elements together is itself a view,
more in general a multi component one.

Combining different elements tries to mimic ranges:

cpp
auto view = registry.view<position>();
auto other = registry.view<velocity>();
const auto &storage = registry.storage<renderable>();

auto pack = view | other | renderable;

The constness of the types is preserved and their order depends on the order in
which the views are combined. For example, the above pack first returns an
instance of position, then one of velocity, and finally one of
renderable.
Since combining elements generates views, a chain can be of arbitrary length and
the above type order rules apply sequentially.

Iteration order

By default, a view is iterated along the pool that contains the smallest number
of elements.
For example, if the registry contains fewer velocitys than it contains
positions, then the order of the elements returned by the following view
depends on how the velocity components are arranged in their pool:

cpp
for(auto entity: registry.view<positon, velocity>()) { 
    // ...
}

Moreover, the order of types when constructing a view does not matter. Neither
does the order of views in a view pack.
However, it is possible to enforce iteration of a view by given component
order by means of the use function:

cpp
auto view = registry.view<position, velocity>();
view.use<position>();

for(auto entity: view) {
    // ...
}

On the other hand, if all a user wants is to iterate the elements in reverse
order, this is possible for a single type view using its reverse iterators:

cpp
auto view = registry.view<position>();

for(auto it = view.rbegin(), last = view.rend(); it != last; ++iter) {
    // ...
}

Unfortunately, multi type views do not offer reverse iterators. Therefore, in
this case it is a must to implement this functionality manually or to use single
type views to lead the iteration.

Runtime views

Multi type views iterate entities that have at least all the given components.
During construction, they look at the number of elements available in each pool
and use the smallest set in order to speed up iterations.
They offer more or less the same functionalities of a multi type view. However,
they do not expose a get member function and users should refer to the
registry that generated the view to access components.
Refer to the inline documentation for all the details.

Runtime views are pretty cheap to construct and should not be stored aside in
any case. They should be used immediately after creation and then they should be
thrown away.
To iterate a runtime view, either use it in a range-for loop:

cpp
entt::runtime_view view{};
view.iterate(registry.storage<position>()).iterate(registry.storage<velocity>());

for(auto entity: view) {
    // ...
}

Or rely on the each member function to iterate entities:

cpp
entt::runtime_view{}
    .iterate(registry.storage<position>())
    .iterate(registry.storage<velocity>())
    .each([](auto entity) {
        // ...
    });

Performance are exactly the same in both cases.
Filtering entities by components is also supported for this kind of views:

cpp
entt::runtime_view view{};
view.iterate(registry.storage<position>()).exclude(registry.storage<velocity>());

Runtime views are meant for when users do not know at compile-time what types to
use to iterate entities. The storage member function of a registry could be
useful in this regard.

Groups

Groups are meant to iterate multiple components at once and to offer a faster
alternative to multi type views.
Groups overcome the performance of the other tools available but require to get
the ownership of components. This sets some constraints on their pools. On the
other hand, groups are not an automatism that increases memory consumption,
affects functionalities and tries to optimize iterations for all the possible
combinations of components. Users can decide when to pay for groups and to what
extent.
The most interesting aspect of groups is that they fit usage patterns. Other
solutions around usually try to optimize everything, because it is known that
somewhere within the everything there are also our usage patterns. However
this has a cost that is not negligible, both in terms of performance and memory
usage. Ironically, users pay the price also for things they do not want and this
is not something I like much. Even worse, one cannot easily disable such a
behavior. Groups work differently instead and are designed to optimize only the
real use cases when users find they need to.
Another nice-to-have feature of groups is that they have no impact on memory
consumption, put aside full non-owning groups that are pretty rare and should be
avoided as long as possible.

All groups affect to an extent the creation and destruction of their components.
This is due to the fact that they must observe changes in the pools of
interest and arrange data correctly when needed for the types they own.
In all cases, a group allows to get the exact number of elements it is going to
return.
Refer to the inline documentation for all the details.

Storing aside groups is not required as they are extremely cheap to create, even
though valid groups can be copied without problems and reused freely.
A group performs an initialization step the very first time it is requested and
this could be quite costly. To avoid it, consider creating the group when no
components have been assigned yet. If the registry is empty, preparation is
extremely fast.

To iterate a group, either use it in a range-for loop:

cpp
auto group = registry.group<position>(entt::get<velocity, renderable>);

for(auto entity: group) {
    // a component at a time ...
    auto &position = group.get<position>(entity);
    auto &velocity = group.get<velocity>(entity);

    // ... multiple components ...
    auto [pos, vel] = group.get<position, velocity>(entity);

    // ... all components at once
    auto [pos, vel, rend] = group.get(entity);

    // ...
}

Or rely on the each member functions to iterate both entities and components
at once:

cpp
// through a callback
registry.group<position>(entt::get<velocity>).each([](auto entity, auto &pos, auto &vel) {
    // ...
});

// using an input iterator
for(auto &&[entity, pos, vel]: registry.group<position>(entt::get<velocity>).each()) {
    // ...
}

Note that entities can also be excluded from the parameter list when received
through a callback and this can improve even further the performance during
iterations.
Since they are not explicitly instantiated, empty components are not returned in
any case.

Note: prefer the get member function of a group instead of that of a
registry during iterations to get the types iterated by the group itself.

Full-owning groups

A full-owning group is the fastest tool a user can expect to use to iterate
multiple components at once. It iterates all the components directly, no
indirection required.
This type of groups performs more or less as if users are accessing sequentially
a bunch of packed arrays of components all sorted identically, with no jumps nor
branches.

A full-owning group is created as:

cpp
auto group = registry.group<position, velocity>();

Filtering entities by components is also supported:

cpp
auto group = registry.group<position, velocity>({}, entt::exclude<renderable>);

Once created, the group gets the ownership of all the components specified in
the template parameter list and arranges their pools as needed.

Sorting owned components is no longer allowed once the group has been created.
However, full-owning groups are sorted using their sort member functions.
Sorting a full-owning group affects all its instances.

Partial-owning groups

A partial-owning group works similarly to a full-owning group for the components
it owns, but relies on indirection to get components owned by other groups.
This is not as fast as a full-owning group, but it is already much faster than a
view when there are only one or two free components to retrieve (the most common
cases likely). In the worst case, it is not slower than views anyway.

A partial-owning group is created as:

cpp
auto group = registry.group<position>(entt::get<velocity>);

Filtering entities by components is also supported:

cpp
auto group = registry.group<position>(entt::get<velocity>, entt::exclude<renderable>);

Once created, the group gets the ownership of all the components specified in
the template parameter list and arranges their pools as needed. The ownership of
the types provided via entt::get does not pass to the group instead.

Sorting owned components is no longer allowed once the group has been created.
However, partial-owning groups are sorted using their sort member functions.
Sorting a partial-owning group affects all its instances.

Non-owning groups

Non-owning groups are usually fast enough, for sure faster than views and well
suited for most of the cases. However, they require custom data structures to
work properly and they increase memory consumption.
As a rule of thumb, users should avoid using non-owning groups, if possible.

A non-owning group is created as:

cpp
auto group = registry.group<>(entt::get<position, velocity>);

Filtering entities by components is also supported:

cpp
auto group = registry.group<>(entt::get<position, velocity>, entt::exclude<renderable>);

The group does not receive the ownership of any type of component in this
case. This type of groups is therefore the least performing in general, but also
the only one that can be used in any situation to slightly improve performance.

Non-owning groups are sorted using their sort member functions. Sorting a
non-owning group affects all its instances.

Types: const, non-const and all in between

The registry class offers two overloads when it comes to constructing views
and groups: a const version and a non-const one. The former accepts only const
types as template parameters, the latter accepts both const and non-const types
instead.
It means that views and groups generated by a const registry also propagate the
constness to the types involved. As an example:

cpp
entt::view<entt::get_t<const position, const velocity>> view = std::as_const(registry).view<const position, const velocity>();

Consider the following definition for a non-const view instead:

cpp
entt::view<entt::get_t<position, const velocity>> view = registry.view<position, const velocity>();

In the example above, view is used to access either read-only or writable
position components while velocity components are read-only in all
cases.
Similarly, these statements are all valid:

cpp
position &pos = view.get<position>(entity);
const position &cpos = view.get<const position>(entity);
const velocity &cpos = view.get<const velocity>(entity);
std::tuple<position &, const velocity &> tup = view.get<position, const velocity>(entity);
std::tuple<const position &, const velocity &> ctup = view.get<const position, const velocity>(entity);

It is not possible to get non-const references to velocity components from the
same view instead. Therefore, these result in compilation errors:

cpp
velocity &cpos = view.get<velocity>(entity);
std::tuple<position &, velocity &> tup = view.get<position, velocity>(entity);
std::tuple<const position &, velocity &> ctup = view.get<const position, velocity>(entity);

The each member functions also propagates constness to its return values:

cpp
view.each([](auto entity, position &pos, const velocity &vel) {
    // ...
});

A caller can still refer to the position components through a const reference
because of the rules of the language that fortunately already allow it.

The same concepts apply to groups as well.

Give me everything

Views and groups are narrow windows on the entire list of entities. They work by
filtering entities according to their components.
In some cases there may be the need to iterate all the entities still in use
regardless of their components. This is done by accessing entity storage:

cpp
for(auto entity: registry.view<entt::entity>()) {
    // ...
}

As a rule of thumb, consider using a view or a group if the goal is to iterate
entities that have a determinate set of components. These tools are usually much
faster than filtering entities with a bunch of custom tests.
In all the other cases, this is the way to go. For example, it is possible to
combine this view with the orphan member function to clean up orphan entities
(that is, entities that are still in use and have no assigned components):

cpp
for(auto entity: registry.view<entt::entity>()) {
    if(registry.orphan(entity)) {
        registry.release(entity);
    }
}

In general, iterating all entities can result in poor performance. It should not
be done frequently to avoid the risk of a performance hit.
However, it is convenient when initializing an editor or to reclaim pending
identifiers.

What is allowed and what is not

Most of the ECS available out there do not allow to create and destroy
entities and components during iterations, nor to have pointer stability.
EnTT partially solves the problem with a few limitations:

  • Creating entities and components is allowed during iterations in most cases
    and it never invalidates already existing references.

  • Deleting the current entity or removing its components is allowed during
    iterations but it could invalidate references. For all the other entities,
    destroying them or removing their iterated components is not allowed and can
    result in undefined behavior.

  • When pointer stability is enabled for the type leading the iteration, adding
    instances of the same type may or may not cause the entity involved to be
    returned. Destroying entities and components is always allowed instead, even
    if not currently iterated, without the risk of invalidating any references.

  • In case of reverse iterations, adding or removing elements is not allowed
    under any circumstances. It could quickly lead to undefined behaviors.

In other terms, iterators are rarely invalidated. Also, component references
are not invalidated when a new element is added while they could be invalidated
upon destruction due to the swap-and-pop policy, unless the type leading the
iteration undergoes in-place deletion.
As an example, consider the following snippet:

cpp
registry.view<position>().each([&](const auto entity, auto &pos) {
    registry.emplace<position>(registry.create(), 0., 0.);
    // references remain stable after adding new instances
    pos.x = 0.;
});

The each member function will not break (because iterators remain valid) nor
will any reference be invalidated. Instead, more attention should be paid to the
destruction of entities or the removal of components.
Use a common range-for loop and get components directly from the view or move
the deletion of entities and components at the end of the function to avoid
dangling pointers.

For all types that do not offer stable pointers, iterators are also invalidated
and the behavior is undefined if an entity is modified or destroyed and it is
not the one currently returned by the iterator nor a newly created one.
To work around it, possible approaches are:

  • Store aside the entities and the components to be removed and perform the
    operations at the end of the iteration.

  • Mark entities and components with a proper tag component that indicates they
    must be purged, then perform a second iteration to clean them up one by one.

A notable side effect of this feature is that the number of required allocations
is further reduced in most cases.

More performance, more constraints

Groups are a faster alternative to views. However, the higher the performance,
the greater the constraints on what is allowed and what is not.
In particular, groups add in some rare cases a limitation on the creation of
components during iterations. It happens in quite particular cases. Given the
nature and the scope of the groups, it is not something in which it will happen
to come across probably, but it is good to know it anyway.

First of all, it must be said that creating components while iterating a group
is not a problem at all and is done freely as it happens with the views. The
same applies to the destruction of components and entities, for which the rules
mentioned above apply.

The additional limitation arises instead when a given component that is owned by
a group is iterated outside of it. In this case, adding components that are part
of the group itself may invalidate the iterators. There are no further
limitations to the destruction of components and entities.
Fortunately, this is not always true. In fact, it almost never is and only
happens under certain conditions. In particular:

  • Iterating a type of component that is part of a group with a single type view
    and adding to an entity all the components required to get it into the group
    may invalidate the iterators.

  • Iterating a type of component that is part of a group with a multi type view
    and adding to an entity all the components required to get it into the group
    can invalidate the iterators, unless users specify another type of component
    to use to induce the order of iteration of the view (in this case, the former
    is treated as a free type and is not affected by the limitation).

In other words, the limitation does not exist as long as a type is treated as a
free type (as an example with multi type views and partial- or non-owning
groups) or iterated with its own group, but it can occur if the type is used as
a main type to rule on an iteration.
This happens because groups own the pools of their components and organize the
data internally to maximize performance. Because of that, full consistency for
owned components is guaranteed only when they are iterated as part of their
groups or as free types with multi type views and groups in general.

Multithreading

In general, the entire registry is not thread safe as it is. Thread safety is
not something that users should want out of the box for several reasons. Just to
mention one of them: performance.
Views, groups and consequently the approach adopted by EnTT are the great
exception to the rule. It is true that views, groups and iterators in general
are not thread safe by themselves. Because of this users should not try to
iterate a set of components and modify the same set concurrently. However:

  • As long as a thread iterates the entities that have the component X or
    assign and removes that component from a set of entities, another thread can
    safely do the same with components Y and Z and everything work like just
    fine. As a trivial example, users can freely execute the rendering system and
    iterate the renderable entities while updating a physic component concurrently
    on a separate thread.

  • Similarly, a single set of components can be iterated by multiple threads as
    long as the components are neither assigned nor removed in the meantime. In
    other words, a hypothetical movement system can start multiple threads, each
    of which will access the components that carry information about velocity and
    position for its entities.

This kind of entity-component systems can be used in single-threaded
applications as well as along with async stuff or multiple threads. Moreover,
typical thread based models for ECS do not require a fully thread-safe
registry to work. Actually, users can reach the goal with the registry as it is
while working with most of the common models.

Because of the few reasons mentioned above and many others not mentioned, users
are completely responsible for synchronization whether required. On the other
hand, they could get away with it without having to resort to particular
expedients.

Finally, EnTT is configured via a few compile-time definitions to make some of
its parts implicitly thread-safe, roughly speaking only the ones that really
make sense and cannot be turned around.
When using multiple threads with EnTT, you should define ENTT_USE_ATOMIC
unless you know exactly what you are doing. This is true even if each thread
only uses thread local data. For more information, see
this section.

Iterators

A special mention is needed for the iterators returned by views and groups. Most
of the time they meet the requirements of random access iterators, in all cases
they meet at least the requirements of forward iterators.
In other terms, they are suitable for use with the parallel algorithms of the
standard library. If it is not clear, this is a great thing.

As an example, this kind of iterators are used in combination with
std::for_each and std::execution::par to parallelize the visit and therefore
the update of the components returned by a view or a group, as long as the
constraints previously discussed are respected:

cpp
auto view = registry.view<position, const velocity>();

std::for_each(std::execution::par_unseq, view.begin(), view.end(), [&view](auto entity) {
    // ...
});

This can increase the throughput considerably, even without resorting to who
knows what artifacts that are difficult to maintain over time.

Unfortunately, because of the limitations of the current revision of the
standard, the parallel std::for_each accepts only forward iterators. This
means that the default iterators provided by the library cannot return proxy
objects as references and must return actual reference types instead.
This may change in the future and the iterators will almost certainly return
both the entities and a list of references to their components by default sooner
or later. Multi-pass guarantee will not break in any case and the performance
should even benefit from it further.

Const registry

A const registry is also fully thread safe. This means that it is not able to
lazily initialize a missing storage when a view is generated.
The reason for this is easy to explain. To avoid requiring types to be
announced in advance, a registry lazily creates the storage objects for the
different components. However, this is not possible for a thread safe const
registry.

Returned views are always valid and behave as expected in the context of the
caller. However, they may contain dangling references to non-existing storage
when created from a const registry.
As a result, such a view may misbehave over time if it is kept aside for a
second use.
Therefore, if the general advice is to create views when necessary and discard
them immediately afterwards, this becomes almost a rule when it comes to views
generated from a const registry.

Fortunately, there is also a way to instantiate storage classes early when in
doubt or when there are special requirements.
Calling the storage method is equivalent to announcing a particular storage,
so as to avoid running into problems. For those interested, there are also
alternative approaches, such as a single threaded tick for the registry warm-up,
but these are not always applicable.
In this case, views never risk becoming invalid.

Beyond this document

There are many other features and functions not listed in this document.
EnTT and in particular its ECS part is in continuous development and some
things could be forgotten, others could have been omitted on purpose to reduce
the size of this file. Unfortunately, some parts may even be outdated and still
to be updated.

For further information, it is recommended to refer to the documentation
included in the code itself or join the official channels to ask a question.


Md Faq (`docs/md/faq.md`)

Frequently Asked Questions

Table of Contents

Introduction

This is a constantly updated section where I am trying to put the answers to the
most frequently asked questions.
If you do not find your answer here, there are two cases: nobody has done it
yet, or this section needs updating. In both cases, you can
open a new issue or enter either
the gitter channel or the
discord server to ask for help.
Probably someone already has an answer for you and we can then integrate this
part of the documentation.

FAQ

Why is my debug build on Windows so slow?

EnTT is an experimental project that I also use to keep me up-to-date with the
latest revision of the language and the standard library. For this reason, it is
likely that some classes you are working with are using standard containers
under the hood.
Unfortunately, it is known that the standard containers are not particularly
performing in debugging (the reasons for this go beyond this document) and are
even less so on Windows, apparently. Fortunately, this can also be mitigated a
lot, achieving good results in many cases.

First of all, there are two things to do in a Windows project:

  • Disable the /JMC
    option (Just My Code debugging), available starting with Visual Studio 2017
    version 15.8.

  • Set the _ITERATOR_DEBUG_LEVEL
    macro to 0. This will disable checked iterators and iterator debugging.

Moreover, set the ENTT_DISABLE_ASSERT variable or redefine the ENTT_ASSERT
macro to disable internal debug checks in EnTT:

cpp
#define ENTT_ASSERT(...) ((void)0)

These asserts are introduced to help the users, but they require access to the
underlying containers and therefore risk ruining the performance in some cases.

With these changes, debug performance should increase enough in most cases. If
you want something more, you can also switch to an optimization level O0 or
preferably O1.

How can I represent hierarchies with my components?

This is one of the first questions that anyone makes when starting to work with
the entity-component-system architectural pattern.
There are several approaches to the problem, and the best one depends mainly on
the real problem one is facing. In all cases, how to do it does not strictly
depend on the library in use, but the latter certainly allows or not different
techniques depending on how the data are laid out.

I tried to describe some of the approaches that fit well with the model of
EnTT. This is the
first post of a series that tries to explore the problem. More will probably
come in the future.
In addition, EnTT also offers the possibility to create stable storage types
and therefore have pointer stability for one, all or some components. This is by
far the most convenient solution when it comes to creating hierarchies and
whatnot. See the documentation for the ECS part of the library and in particular
what concerns the component_traits class for further details.

Custom entity identifiers: yay or nay?

Custom entity identifiers are definitely a good idea in two cases at least:

  • If std::uint32_t is not large enough for your purposes, since this is the
    underlying type of entt::entity.

  • If you want to avoid conflicts when using multiple registries.

Identifiers can be defined through enum classes and class types that define an
entity_type member of type std::uint32_t or std::uint64_t.
In fact, this is a definition equivalent to that of entt::entity:

cpp
enum class entity: std::uint32_t {};

There is no limit to the number of identifiers that can be defined.

Warning C4003: the min, the max and the macro

On Windows, a header file defines two macros min and max which may result in
conflicts with their counterparts in the standard library and therefore in
errors during compilation.

It is a pretty big problem. However, fortunately it is not a problem of EnTT
and there is a fairly simple solution to it.
It consists in defining the NOMINMAX macro before including any other header
so as to get rid of the extra definitions:

cpp
#define NOMINMAX

Please refer to this issue for
more details.

The standard and the non-copyable types

EnTT uses internally the trait std::is_copy_constructible_v to check if a
component is actually copyable. However, this trait does not really check
whether a type is actually copyable. Instead, it just checks that a suitable
copy constructor and copy operator exist.
This can lead to surprising results due to some idiosyncrasies of the standard.

For example, std::vector defines a copy constructor that is conditionally
enabled depending on whether the value type is copyable or not. As a result,
std::is_copy_constructible_v returns true for the following specialization:

cpp
struct type {
    std::vector<std::unique_ptr<action>> vec;
};

However, the copy constructor is effectively disabled upon specialization.
Therefore, trying to assign an instance of this type to an entity may trigger a
compilation error.
As a workaround, users can mark the type explicitly as non-copyable. This also
suppresses the implicit generation of the move constructor and operator, which
will therefore have to be defaulted accordingly:

cpp
struct type {
    type(const type &) = delete;
    type(type &&) = default;

    type & operator=(const type &) = delete;
    type & operator=(type &&) = default;

    std::vector<std::unique_ptr<action>> vec;
};

Note that aggregate initialization is also disabled as a consequence.
Fortunately, this type of trick is quite rare. The bad news is that there is no
way to deal with it at the library level, this being due to the design of the
language. On the other hand, the fact that the language itself also offers a way
to mitigate the problem makes it manageable.

Which functions trigger which signals

Storage classes offer three signals that are emitted following specific
operations. Maybe not everyone knows what these operations are, though.
If this is not clear, below you can find a vademecum for this purpose:

  • on_created is invoked when a component is first added (neither modified nor
    replaced) to an entity.

  • on_update is called whenever an existing component is modified or replaced.

  • on_destroyed is called when a component is explicitly or implicitly removed
    from an entity.

Among the most controversial functions can be found emplace_or_replace and
destroy. However, following the above rules, it is quite simple to know what
will happen.
In the first case, on_created is invoked if the entity has not the component,
otherwise the latter is replaced and therefore on_update is triggered. As for
the second case, components are removed from their entities and thus freed when
they are recycled. It means that on_destroyed is triggered for every component
owned by the entity that is destroyed.

Duplicate storage for the same component

It is rare, but you can see double sometimes, especially when it comes to
storage. This can be caused by a conflict in the hash assigned to the various
component types (one of a kind) or by bugs in your compiler
(more common apparently).
Regardless of the cause, EnTT offers a customization point that also serves as
a solution in this case:

cpp
template<>
struct entt::type_hash<Type> final {
    [[nodiscard]] static consteval id_type value() noexcept {
        return hashed_string::value("Type");
    }

    [[nodiscard]] consteval operator id_type() const noexcept {
        return value();
    }
};

Specializing type_hash directly bypasses the default implementation offered by
EnTT, thus avoiding any possible conflicts or compiler bugs.


Md Graph (`docs/md/graph.md`)

Crash Course: graph

Table of Contents

Introduction

EnTT does not aim to offer everything one needs to work with graphs.
Therefore, anyone looking for this in the graph submodule will be
disappointed.
Quite the opposite is true though. This submodule is minimal and contains only
the data structures and algorithms strictly necessary for the development of
some tools such as the flow builder.

Data structures

As anticipated in the introduction, the aim is not to offer all possible data
structures suitable for representing and working with graphs. Many will likely
be added or refined over time. However, I want to discourage anyone expecting
tight scheduling on the subject.
The data structures presented in this section are mainly useful for the
development and support of some tools that are also part of the same submodule.

Adjacency matrix

The adjacency matrix is designed to represent either a directed or an undirected
graph:

cpp
entt::adjacency_matrix<entt::directed_tag> adjacency_matrix{};

The directed_tag type creates the graph as directed. There is also an
undirected_tag counterpart which creates it as undirected.
The interface deviates slightly from the typical double indexing of C and offers
an API that is perhaps more familiar to a C++ programmer. Therefore, the access
and modification of an element takes place via the contains, insert and
erase functions rather than a double call to an operator[]:

cpp
if(adjacency_matrix.contains(0u, 1u)) {
    adjacency_matrix.erase(0u, 1u);
} else {
    adjacency_matrix.insert(0u, 1u);
}

Both insert and erase are idempotent functions which have no effect if the
element already exists or has already been deleted.
The first one returns an std::pair containing the iterator to the element and
a boolean value indicating whether the element was newly inserted or not. The
second one returns the number of deleted elements (0 or 1).

An adjacency matrix is initialized with the number of elements (vertices) when
constructing it but can also be resized later using the resize function:

cpp
entt::adjacency_matrix<entt::directed_tag> adjacency_matrix{3u};

To visit all vertices, the class offers a function named vertices that returns
an iterable object suitable for the purpose:

cpp
for(auto &&vertex: adjacency_matrix.vertices()) {
    // ...
}

The same result is obtained with the following snippet, since the vertices are
plain unsigned integral values:

cpp
for(auto last = adjacency_matrix.size(), pos = {}; pos < last; ++pos) {
    // ...
}

As for visiting the edges, a few functions are available.
When the purpose is to visit all the edges of a given adjacency matrix, the
edges function returns an iterable object that is used to get them as pairs of
vertices:

cpp
for(auto [lhs, rhs]: adjacency_matrix.edges()) {
    // ...
}

If the goal is to visit all the in- or out-edges of a given vertex instead, the
in_edges and out_edges functions are meant for that:

cpp
for(auto [lhs, rhs]: adjacency_matrix.out_edges(3u)) {
    // ...
}

Both the functions expect the vertex to visit (that is, to return the in- or
out-edges for) as an argument.
Finally, the adjacency matrix is an allocator-aware container and offers most of
the functionalities one would expect from this type of containers, such as
clear or get_allocator and so on.

Graphviz dot language

As it is one of the most popular formats, the library offers minimal support for
converting a graph to a Graphviz dot snippet.
The simplest way is to pass both an output stream and a graph to the dot
function:

cpp
std::ostringstream output{};
entt::dot(output, adjacency_matrix);

It is also possible to provide a callback to which the vertices are passed and
which can be used to add (dot) properties to the output as needed:

cpp
std::ostringstream output{};

entt::dot(output, adjacency_matrix, [](auto &output, auto vertex) {
    out << "label=\"v\"" << vertex << ",shape=\"box\"";
});

This second mode is particularly convenient when the user wants to associate
externally managed data to the graph being converted.

Flow builder

A flow builder is used to create execution graphs from tasks and resources.
The implementation is as generic as possible and does not bind to any other part
of the library.

This class is designed as a sort of state machine to which a specific task is
attached for which the resources accessed in read-only or read-write mode are
specified.
Most of the functions in the API also return the flow builder itself, according
to what is the common sense API when it comes to builder classes.

Once all tasks are registered and resources assigned to them, an execution graph
in the form of an adjacency matrix is returned to the user.
This graph contains all the tasks assigned to the flow builder in the form of
vertices. The vertex itself is used as an index to get the identifier passed
during registration.

Tasks and resources

Although these terms are used extensively in the documentation, the flow builder
has no real concept of tasks and resources.
This class works mainly with identifiers, that is, values of type id_type.
In other terms, both tasks and resources are identified by integral values.
This allows not to couple the class itself to the rest of the library or to any
particular data structure. On the other hand, it requires the user to keep track
of the association between identifiers and operations or actual data.

Once a flow builder is created (which requires no constructor arguments), the
first thing to do is to bind a task. This tells the builder who intends to
consume the resources that are specified immediately after:

cpp
entt::flow builder{};
builder.bind("task_1"_hs);

The example uses the EnTT hashed string to generate an identifier for the
task.
Indeed, the use of id_type as an identifier type is not by accident. In fact,
it matches well with the internal hashed string class. Moreover, it is also the
same type returned by the hash function of the internal RTTI system, in case the
user wants to rely on that.
However, being an integral value, it leaves the user full freedom to rely on his
own tools if necessary.

Once a task is associated with the flow builder, it has also assigned read-only
or read-write resources as appropriate:

cpp
builder
    .bind("task_1"_hs)
        .ro("resource_1"_hs)
        .ro("resource_2"_hs)
    .bind("task_2"_hs)
        .rw("resource_2"_hs)

As mentioned, many functions return the builder itself, and it is therefore easy
to concatenate the different calls.
Also in the case of resources, they are identified by numeric values of type
id_type. As above, the choice is not entirely random. This goes well with the
tools offered by the library while leaving room for maximum flexibility.

Finally, both the ro and rw functions also offer an overload that accepts a
pair of iterators, so that one can pass a range of resources in one go.

Rebinding

The flow class is resource based rather than task based. This means that graph
generation is driven by resources and not by the order of appearance of tasks
during flow definition.
Although this concept is particularly important, it is almost irrelevant for the
vast majority of cases. However, it becomes relevant when rebinding resources
or tasks.

In fact, nothing prevents rebinding elements to a flow.
However, the behavior changes slightly from case to case and has some nuances
that it is worth knowing about.

Directly rebinding a resource without the task being replaced trivially results
in the task's access mode for that resource being updated:

cpp
builder.bind("task"_hs).rw("resource"_hs).ro("resource"_hs)

In this case, the resource is accessed in read-only mode, regardless of the
first call to rw.
Behind the scenes, the call does not actually replace the previous one but is
queued after it instead, overwriting it when generating the graph. Thus, a large
number of resource rebindings may even impact processing times (very difficult
to observe but theoretically possible).

Rebinding resources and also combining it with changes to tasks has far more
implications instead.
As mentioned, graph generation takes place starting from resources and not from
tasks. Therefore, the result may not be as expected:

cpp
builder
    .bind("task_1"_hs)
        .ro("resource"_hs)
    .bind("task_2"_hs)
        .ro("resource"_hs)
    .bind("task_1"_hs)
        .rw("resource"_hs);

What happens here is that the resource first sees a read-only access request
from the first task, then a read-only request from the second task and finally
a read-write request from the first task.
Although this definition would probably be counted as an error, the resulting
graph may be unexpected. In fact, this consists of a single edge outgoing from
the second task and directed to the first task.
To intuitively understand what happens, it is enough to think of the fact that a
task never has an edge pointing to itself.

While not obvious, this approach has its pros and cons like any other solution.
For example, creating loops is actually simple in the context of resource-based
graph generations:

cpp
builder
    .bind("task_1"_hs)
        .rw("resource"_hs)
    .bind("task_2"_hs)
        .rw("resource"_hs)
    .bind("task_1"_hs)
        .rw("resource"_hs);

As expected, this definition leads to the creation of two edges that define a
loop between the two tasks.

As a general rule, rebinding resources and tasks is highly discouraged because
it could lead to subtle bugs if users do not know what they are doing.
However, once the mechanisms of resource-based graph generation are understood,
it can offer to the expert user flexibility and a range of possibilities
otherwise inaccessible.

Fake resources and order of execution

The flow builder does not offer the ability to specify when a task should run
before or after another task.
In fact, the order of registration on the resources also determines the order
in which the tasks are processed during the generation of the execution graph.

However, there is a way to force the execution order of two processes.
Briefly, since accessing a resource in opposite modes requires sequential rather
than parallel scheduling, it is possible to make use of fake resources to rule
on the execution order:

cpp
builder
    .bind("task_1"_hs)
        .ro("resource_1"_hs)
        .rw("fake"_hs)
    .bind("task_2"_hs)
        .ro("resource_2"_hs)
        .ro("fake"_hs)
    .bind("task_3"_hs)
        .ro("resource_2"_hs)
        .ro("fake"_hs)

This snippet forces the execution of task_1 before task_2 and task_3.
This is due to the fact that the former sets a read-write requirement on a fake
resource that the other tasks also want to access in read-only mode.
Similarly, it is possible to force a task to run after a certain group:

cpp
builder
    .bind("task_1"_hs)
        .ro("resource_1"_hs)
        .ro("fake"_hs)
    .bind("task_2"_hs)
        .ro("resource_1"_hs)
        .ro("fake"_hs)
    .bind("task_3"_hs)
        .ro("resource_2"_hs)
        .rw("fake"_hs)

In this case, since there are a number of processes that want to read a specific
resource, they will do so in parallel by forcing task_3 to run after all the
other tasks.

Sync points

Sometimes it is useful to assign the role of sync point to a node.
Whether it accesses new resources or is simply a watershed, the procedure for
assigning this role to a vertex is always the same. First it is tied to the flow
builder, then the sync function is invoked:

cpp
builder.bind("sync_point"_hs).sync();

The choice to assign an identity to this type of node lies in the fact that,
more often than not, they also perform operations on resources.
If this is not the case, it will still be possible to create no-op vertices to
which empty tasks are assigned.

Execution graph

Once both the resources and their consumers have been properly registered, the
purpose of this tool is to generate an execution graph that takes into account
all specified constraints to return the best scheduling for the vertices:

cpp
entt::adjacency_matrix<entt::directed_tag> graph = builder.graph();

Searching for the main vertices (that is, those without in-edges) is usually the
first thing required:

cpp
for(auto &&vertex: graph) {
    if(auto in_edges = graph.in_edges(vertex); in_edges.begin() == in_edges.end()) {
        // ...
    }
}

Then it is possible to instantiate an execution graph by means of other
functions such as out_edges to retrieve the children of a given task or
edges to get the identifiers.


Md Lib (`docs/md/lib.md`)

Push EnTT across boundaries

Table of Contents

Working across boundaries

EnTT has historically had a limit when used across boundaries on Windows in
general and on GNU/Linux when default visibility was set to hidden. The
limitation was mainly due to a custom utility used to assign unique, sequential
identifiers with different types.
Fortunately, nowadays EnTT works smoothly across boundaries.

Smooth until proven otherwise

Many classes in EnTT make extensive use of type erasure for their purposes.
This raises the need to identify objects whose type has been erased.
The type_hash class template is how identifiers are generated and thus made
available to the rest of the library. In general, this class arouses little
interest. The only exception is when a conflict between identifiers occurs
(definitely uncommon though) or when the default solution proposed by EnTT is
not suitable for the user's purposes.
The section dedicated to type_info contains all the details to get around the
issue in a concise and elegant way. Please refer to the specific documentation.

When working with linked libraries, compile definitions ENTT_API_EXPORT and
ENTT_API_IMPORT are there to import or export symbols, so as to make
everything work nicely across boundaries.
On the other hand, everything should run smoothly when working with plugins or
shared libraries that do not export any symbols.

For those who need more details, the test suite contains many examples covering
the most common cases (see the lib directory for all details).
It goes without saying that it is impossible to cover all possible cases.
However, what is offered should hopefully serve as a basis for all of them.

Meta context

The runtime reflection system deserves a special mention when it comes to using
it across boundaries.
Since it is linked already to a static context to which the elements are
attached and different contexts do not relate to each other, they must be
shared to allow the use of meta types across boundaries.

Fortunately, sharing a context is also trivial to do. First of all, the local
one is acquired in the main space:

cpp
auto handle = entt::locator<entt::meta_ctx>::handle();

Then, it is passed to the receiving space that sets it as its default context,
thus discarding or storing aside the local one:

cpp
entt::locator<entt::meta_ctx>::reset(handle);

From now on, both spaces refer to the same context and to it are all new meta
types attached, no matter where they are created.
Note that replacing the main context does not also propagate changes across
boundaries. In other words, replacing a context results in the decoupling of the
two sides and therefore a divergence in the contents.

Memory Management

There is another subtle problem due to memory management that can lead to
headaches.
It can occur where there are pools of objects (such as components or events)
dynamically created on demand. This is usually not a problem when working with
linked libraries that rely on the same dynamic runtime. However, it can occur in
the case of plugins or statically linked runtimes.

As an example, imagine creating an instance of registry in the main executable
and sharing it with a plugin. If the latter starts working with a component that
is unknown to the former, a dedicated pool is created within the registry on
first use.
As one can guess, this pool is instantiated on a different side of the boundary
from the registry. Therefore, the instance is now managing memory from
different spaces, and this can quickly lead to crashes if not properly
addressed.

To overcome the risk, it is recommended to use well-defined interfaces that make
fundamental types pass through the boundaries, isolating the instances of the
EnTT classes from time to time and as appropriate.
Refer to the test suite for some examples, read the documentation available
online about this type of issues or consult someone who has already had such
experiences to avoid problems.


EnTT in Action

Table of Contents

Introduction

EnTT is widely used in private and commercial applications. I cannot even
mention most of them because of some signatures I put on some documents time
ago. Fortunately, there are also people who took the time to implement open
source projects based on EnTT and did not hold back when it came to
documenting them.

Below an incomplete list of games, applications and articles that can be used as
a reference.
Where I put the word apparently means that the use of EnTT is documented but
the authors did not make explicit announcements or contacted me directly.

If you know of other resources out there that are about EnTT, feel free to
open an issue or a PR. I will be glad to add them to this page.
I hope the following lists can grow much more in the future.

EnTT in Action

Games

  • Minecraft by
    Mojang: of course, that Minecraft, see the
    open source attributions page for more details.
  • Minecraft Legends by
    Mojang: an action strategy game where users have to
    fight to defend the Overworld.
  • Minecraft Earth by
    Mojang: an augmented reality game for mobile, that
    lets users bring Minecraft into the real world.
  • Ember Sword: a modern Free-to-Play MMORPG with a
    player-driven economy, a classless combat system, and scarce, tradable
    cosmetic collectibles.
  • Apparently Diablo II: Resurrected by
    Blizzard: monsters, heroes, items, spells, all
    resurrected. Thanks unknown insider.
  • Apparently
    Call of Duty: Vanguard by
    Sledgehammer Games: I can neither
    confirm nor deny, but there is a license I know in the credits.
  • Apparently D&D Dark Alliance by
    Wizards of the Coast: your party, their
    funeral.
  • TiltedEvolution by
    Tilted Phoques: Skyrim and Fallout 4 mod
    to play online.
  • Antkeeper: an ant colony
    simulation game.
  • Openblack: open source
    reimplementation of the game Black & White (2001).
  • Land of the Rair: the new backend
    of a retro-style MUD for the new age.
  • Face Smash:
    a game to play with your face.
  • EnTT Pacman: an example of how
    to make Pacman with EnTT.
  • Wacman: a pacman clone with OpenGL.
  • Classic Tower Defence:
    a tiny little tower defence game featuring a homemade font.
    Check it out.
  • The Machine: a box pushing
    puzzler with logic gates and other cool stuff.
    Check it out.
  • EnTTPong: a basic game made to showcase
    different parts of EnTT and C++17.
  • Randballs: simple SFML and EnTT
    playground.
  • EnTT Tower Defense: a data oriented tower
    defense example.
  • EnTT Breakout: simple
    example of a breakout game, using SDL and EnTT.
  • Arcade puzzle game with EnTT:
    arcade puzzle game made in C++ using the SDL2 and EnTT libraries.
  • Snake with EnTT: simple snake game
    made in C++ with the SDL2 and EnTT libraries.
  • Mirrors lasers and robots:
    a small tower defense game based on mirror orientation.
  • PopHead: 2D, Zombie,
    RPG game made from scratch in C++.
  • Robotligan: multiplayer
    football game.
  • DungeonSlayer: 2D game made
    from scratch in C++.
  • 3DGame: 2.5D top-down space shooter.
  • Pulcher: 2D cross-platform game inspired
    by Quake.
  • Destroid: one-bazillionth
    arcade game about shooting dirty rocks in space, inspired by Asteroids.
  • Wanderer: a 2D
    exploration-based indie game.
  • Spelunky® Classic remake: a truly
    multiplatform experience with a rewrite from scratch.
  • CubbyTower: a simple tower
    defense game using C++ with Entity Component System (ECS).
  • Runeterra: Legends of Runeterra
    simulator using C++ with some reinforcement learning.
  • Black Sun: fly your
    spaceship through a large 2D open world.
  • PokeMaster: Pokémon Battle
    simulator using C++ with some reinforcement learning.
  • HomeHearth: choose your hero, protect the
    town, before it is too late.
  • City Builder Game: a simple
    city-building game using C++ and OpenGL.
  • BattleSub: two player 2D submarine
    game with some fluid dynamics.
  • Crimson Rush: a dungeon-crawler and
    rougelike inspired game about exploring and surviving as long as possible.
  • Space Fight: one screen
    multi-player arcade shooter game prototype.
  • Confetti Party: C++ sample
    application as a starting point using EnTT and SDL2.
  • Hellbound: a top-down action rogue-like
    where to fight colossal demons in procedurally generated levels of hell.
  • Saurian Sorcery: a tower defense
    game where to assemble a tribe of lizards to defend against robot invaders.
  • robotfindskitten: a clone
    of robotfindskitten inside Notepad.exe, powered by EnTT.
  • Orion: Outer-space Research and
    Interstellar Observation Network (a space shooter game).
  • EnTT Boids: a simple boids
    implementation using EnTT and Raylib.
  • PalmRide: After Flight:
    an on-rails shooter with retro outrun aesthetics.
  • Exhibition of Speed:
    build your own car and go racing.
  • Lichgate: top-down action rogue-like where
    users unlock abilities to fight horde of enemies in an endless world.
  • Letalka:
    small demo game with ships and bullets flying everywhere on the screen.
  • Lichgate: step into the robes of a powerful
    mage determined to halt the relentless hordes of undead.
  • You Are Circle:
    a roguelite top-down shooter with a high-contrast vector line aesthetic.
  • EnTT Dino: a Dinosaur Game
    clone in C++ using only SDL2 and EnTT.
  • Bim!: a last-man-standing arcade online
    game for Android.
  • MonsterWar: a tower defense game
    developed in C++ with SDL3, EnTT, and a few other libraries.

Engines and the like:

  • Hazel Engine: a work in progress
    engine created by The Cherno during
    one of his most famous video series.
  • Aether Engine
    v1.1+ by
    Hadean: a library designed for spatially partitioning
    agent-based simulations.
  • Fling Engine: a Vulkan game
    engine with a focus on data oriented design.
  • NovusCore: a modern take on World
    of Warcraft emulation.
  • Chrysalis: action RPG SDK for
    CRYENGINE games.
  • LM-Engine: the Vim of game
    engines.
  • Edyn: a real-time physics engine
    organized as an ECS.
  • MushMachine: engine...
    vrooooommm.
  • Antara Gaming SDK:
    the Komodo Gaming Software Development Kit.
  • XVP:
    eXpansive Vehicle Physics
    plugin for Unreal Engine.
  • Apparently
    Wisp by
    Team Wisp: an advanced real-time ray tracing
    renderer built for the demands of video game artists.
  • shiva: modern C++ engine with
    modularity.
  • ImGui/EnTT editor:
    a drop-in, single-file entity editor for EnTT that uses ImGui as
    graphical backend (with
    demo code).
  • SgOgl: a game engine library for OpenGL
    developed for educational purposes.
  • Lumos: game engine written in C++
    using OpenGL and Vulkan.
  • Silvanus: Silvanus Fusion 360
    Box Generator.
  • Lina Engine: an open-source,
    modular, tiny and fast C++ game engine, aimed to develop 3D desktop games.
  • Spike: a powerful game engine which
    can run on a toaster.
  • Helena Framework: a modern
    framework in C++17 for backend development.
  • Unity/EnTT: tech demo of a
    native simulation layer using EnTT and Unity as a rendering engine.
  • OverEngine: an overengineered
    game engine.
  • Electro: high performance
    3D game engine with a high emphasis on rendering.
  • Kawaii: a modern data
    oriented game engine.
  • Becketron: a game engine
    written mostly in C++.
  • Spatial Engine: a
    cross-platform engine created on top of Google's filament rendering engine.
  • Kaguya: D3D12 Rendering Engine.
  • OpenAWE: open implementation
    of the Alan Wake Engine.
  • Nazara Engine: fast,
    cross-platform, object-oriented API to help in daily developer life.
  • Billy Engine: some kind of 2D
    engine based on SDL2 and EnTT.
  • Ducktape: an open source C++
    2D & 3D game engine that focuses on being fast and powerful.
  • The Worst Engine: a game engine based on
    OpenGL.
  • Ecsact: a language aimed at describing ECS, with a
    runtime implementation based
    on EnTT.
  • AGE (Arc Game Engine): an
    open-source engine for building 2D & 3D real-time rendering and interactive
    contents.
  • Kengine: the Koala engine is a game
    engine entirely implemented as an entity-component-system.
  • Scion2D: 2D game engine with
    YouTube series
    included.
  • EnTT Editor: an editor for EnTT
    libary that combines its built-in reflection system with ImGui.
  • Era Game Engine: a modern
    ECS-based game engine.
  • Core SDK of Trollworks engine: 2D
    game engine based on procrastination.
  • Rocky: 3D geospatial application
    engine.
  • Donner: a modern C++20 SVG2 rendering
    API with CSS3.
  • Coral Engine: open-source
    student engine with the tools to make games in C++ and Visual scripting.
  • Star Engine: an Advanced C++
    DirectX 11 game engine.
  • Darmok: another C++ game engine.
  • Magique: 2D game engine for programmers
    (or those yet to be).
  • Physecs: real-time 3D rigid body
    physics simulation built on EnTT.
  • KODZA: A work in progress game engine.
  • Omnix: An under-development,
    multi-purpose 3D engine for macOS, using EnTT for the ECS.

Articles, videos and blog posts:

Any Other Business:


Md Locator (`docs/md/locator.md`)

Crash Course: service locator

Table of Contents

Introduction

Usually, service locators are tightly bound to the services they expose. It is
hard to define a general purpose solution.
This tiny class tries to fill the gap and gets rid of the burden of defining a
different specific locator for each application.

Service locator

The service locator API tries to mimic that of std::optional and adds some
extra functionalities on top of it such as allocator support.
There are a couple of functions to set up a service, namely emplace and
allocate_emplace:

cpp
entt::locator<interface>::emplace<service>(argument);
entt::locator<interface>::allocate_emplace<service>(allocator, argument);

The difference is that the latter expects an allocator as the first argument and
uses it to allocate the service itself.
Once a service is set up, it is retrieved using the value function:

cpp
interface &service = entt::locator<interface>::value();

Since the service may not be set (and therefore this function may result in an
undefined behavior), the has_value and value_or functions are also available
to test a service locator and to get a fallback service in case there is none:

cpp
if(entt::locator<interface>::has_value()) {
    // ...
}

interface &service = entt::locator<interface>::value_or<fallback_impl>(argument);

All arguments are used only if necessary, that is, if a service does not already
exist and therefore the fallback service is constructed and returned. In all
other cases, they are discarded.
Finally, to reset a service, use the reset function.

Opaque handles

Sometimes it is useful to transfer a copy of a service to another locator. For
example, when working across boundaries it is common to share a service with a
dynamically loaded module.
Options are not much in this case. Among these is the possibility of exporting
services and assigning them to a different locator.

This is what the handle and reset functions are meant for.
The former returns an opaque object useful for exporting (or rather, obtaining
a reference to) a service. The latter also accepts an optional argument to a
handle which then allows users to reset a service by initializing it with an
opaque handle:

cpp
auto handle = entt::locator<interface>::handle();
entt::locator<interface>::reset(handle);

It is worth noting that it is possible to get handles for uninitialized services
and use them with other locators. Of course, all a user will get is to have an
uninitialized service elsewhere as well.

Note that exporting a service allows users to share the object currently set
in a locator. Replacing it will not replace the element, even where a service
has been configured with a handle to the previous item.
In other words, if an audio service is replaced with a null object to silence an
application and the original service was shared, this operation will not
propagate to the other locators. Therefore, a module that shares the ownership
of the original audio service is still able to emit sounds.


Md Meta (`docs/md/meta.md`)

Crash Course: runtime reflection system

Table of Contents

Introduction

Reflection (or rather, its lack) is a trending topic in the C++ world and a tool
that can unlock a lot of interesting features in the specific case of EnTT. I
looked for a third-party library that met my needs on the subject, but I always
came across some details that I did not like: macros, being intrusive, too many
allocations, and so on.
I finally decided to write a built-in, non-intrusive and macro-free runtime
reflection system for EnTT. Maybe I did not do better than others or maybe
yes, time will tell me, but at least I can model this tool around the library to
which it belongs and not the opposite.

Identifiers

The meta system does not force users to rely on the tools provided by the
library when it comes to working with identifiers. It does this by offering an
API that works with integral values that may or may not be generated by means of
a hashed string.
This means that users can assign any type of identifier to the meta objects, as
long as they are numeric. It does not matter if they are generated at runtime,
at compile-time or with custom functions.

That being said, some of the examples in the following sections are based on the
hashed_string class as provided by this library. Therefore, where an integral
identifier is provided, it is likely that a user defined literal is used as
follows:

cpp
entt::meta_factory<my_type>{}.type("reflected_type"_hs);

For what it is worth, this is completely equivalent to:

cpp
entt::meta_factory<my_type>{}.type(42u);

Obviously, human-readable identifiers are more convenient to use and highly
recommended.

Reflection in a nutshell

Reflection always starts from actual C++ types. Users cannot reflect imaginary
types.
The meta_factory class is where it all starts:

cpp
entt::meta_factory<my_type> factory{};

The returned value is a factory object to use to continue building the meta
type.

By default, a meta type is associated with the identifier returned by the
runtime type identification system built-in in EnTT.
However, it is also possible to assign custom identifiers upon registration to
meta types:

cpp
entt::meta_factory<my_type>{"reflected_type"_hs};

By doing this, the type based lookup no longer finds the specified meta type.
Instead, only the identifier based lookup returns with success.
For the same reason, it is also possible to create multiple meta types for the
same C++ type.

In addition to identifiers, users can assign aliases to meta types. An alias
must be unique and not conflict with existing identifiers.
Meta type lookup attempts to resolve identifiers via aliases if there is no
matching key:

cpp
entt::meta_factory<my_type>{}.type("reflected_type"_hs);

However, users can be interested in adding features to a reflected type so that
the reflection system can use it correctly under the hood, while they do not
want to also make the type searchable.
In this case, it is sufficient not to invoke type.

A factory is such that all its member functions return the factory itself. It is
generally used to create the following:

  • Constructors. A constructor is assigned to a reflected type by specifying
    its list of arguments. Free functions are also accepted if the return type
    is the expected one. From a client perspective, nothing changes between a free
    function or an actual constructor:

    cpp
    entt::meta_factory<my_type>{}.ctor<int, char>().ctor<&factory>();

    Meta default constructors are implicitly generated, if possible.

  • Data members. Meta data members are actual data members of the underlying
    type but also static and global variables or constants of any kind. From the
    point of view of the client, all the variables associated with the reflected
    type appear as if they were part of the type itself:

    cpp
    entt::meta_factory<my_type>{}
        .data<&my_type::static_variable>("static"_hs)
        .data<&my_type::data_member>("member"_hs)
        .data<&global_variable>("global"_hs);

    The data function requires the identifier to use for the meta data member.
    This is then used for runtime access.
    Data members are also defined by means of a setter and getter pair. These are
    either free functions, class members or a mix of them. This approach is also
    convenient to create read-only properties from a non-const data member:

    cpp
    entt::meta_factory<my_type>{}.data<nullptr, &my_type::data_member>("member"_hs);

    Setters and getters take any number of arguments, making the meta member more
    like a function than a true member, while still retaining its nature.
    This is especially convenient when constructing complex members or indexing
    values into underlying structures of various kinds.

  • Member functions. Meta member functions are actual member functions of the
    underlying type but also plain free functions. From the point of view of the
    client, all the functions associated with the reflected type appear as if they
    were part of the type itself:

    cpp
    entt::meta_factory<my_type>{}
        .func<&my_type::static_function>("static"_hs)
        .func<&my_type::member_function>("member"_hs)
        .func<&free_function>("free"_hs);

    The func function requires the identifier to use for the meta data function.
    This is then used for runtime access.
    Overloading of meta functions is supported. Overloaded functions are resolved
    at runtime by the reflection system according to the types of the arguments.

  • Base classes. A base class is such that the underlying type is actually
    derived from it:

    cpp
    entt::meta_factory<derived_type>{}.base<base_type>();

    The reflection system tracks the relationship and allows for implicit casts at
    runtime when required. In other terms, wherever a base_type is required, an
    instance of derived_type is also accepted.

  • Conversion functions. Conversion functions allow users to define conversions
    that are implicitly performed by the reflection system when required:

    cpp
    entt::meta_factory<double>{}.conv<int>();

This is everything users need to create meta types. Refer to the inline
documentation for further details.

Any to the rescue

The reflection system offers a kind of extended version of the entt::any
class (see the core module for more details).
The purpose is to add some feature on top of those already present, so as to
integrate it with the meta type system without having to duplicate the code.

The API is very similar to that of the any type. The class meta_any wraps
many of the feature to infer a meta node, before forwarding some or all of the
arguments to the underlying storage.
Among the few relevant differences, meta_any adds support for containers and
pointer-like types, while any does not.
Similar to any, this class is also used to create aliases for unmanaged
objects either with forward_as_meta or using the std::in_place_type<T &>
disambiguation tag, as well as from an existing object by means of the as_ref
member function. Additionally, it can take ownership of pointers passed as
arguments along with std::in_place.
Unlike any instead, meta_any treats an empty instance and one initialized
with void differently:

cpp
entt::meta_any empty{};
entt::meta_any other{std::in_place_type<void>};

While any considers both as empty, meta_any treats objects initialized with
void as if they were valid ones. This allows differentiating between failed
function calls and function calls that are successful but return nothing.

Finally, the member functions try_cast, cast and allow_cast are used to
cast the underlying object to a given type (either a reference or a value type)
or to convert a meta_any in such a way that a cast becomes viable for the
resulting object.
There is in fact no any_cast equivalent for meta_any.

Enjoy the runtime

Once the web of reflected types is constructed, it is a matter of using it at
runtime where required.
There are a few options to search for a reflected type:

cpp
// direct access to a reflected type
auto by_type = entt::resolve<my_type>();

// look up a reflected type by identifier
auto by_id = entt::resolve("reflected_type"_hs);

// look up a reflected type by type info
auto by_type_id = entt::resolve(entt::type_id<my_type>());

There exists also an overload of the resolve function to use to iterate all
reflected types at once. It returns an iterable object to be used in a range-for
loop:

cpp
for(auto &&[id, type]: entt::resolve()) {
    // ...
}

In all cases, the returned value is an instance of meta_type (possibly with
its id). These objects offer an API to know their runtime identifiers, to
iterate all the meta objects associated with them and even to build instances of
the underlying type.
Meta data members and functions are accessed by means of their identifiers:

  • Meta data members:

    cpp
    auto data = entt::resolve<my_type>().data("member"_hs);

    The returned type is meta_data and may be invalid if there is no meta data
    object associated with the given identifier.
    A meta data object offers an API to query the underlying type (for example, to
    know if it is a const or a static one), to get the meta type of the variable
    and to set or get the contained value.

  • Meta function members:

    cpp
    auto func = entt::resolve<my_type>().func("member"_hs);

    The returned type is meta_func and may be invalid if there is no meta
    function object associated with the given identifier.
    A meta function object offers an API to query the underlying type (for
    example, to know if it is a const or a static function), to know the number of
    arguments, the meta return type and the meta types of the parameters. In
    addition, a meta function object is used to invoke the underlying function and
    then get the return value in the form of a meta_any object.

Both functions search for the elements throughout the meta type hierarchy.
However, they offer the option of passing a second boolean argument to stop the
search at the top-level meta type.
All the meta objects thus obtained as well as the meta types explicitly convert
to a boolean value to check for validity:

cpp
if(auto func = entt::resolve<my_type>().func("member"_hs); func) {
    // ...
}

Furthermore, all of them (and a few more, like meta basis) are returned by a
bunch of overloads that provide the caller with iterable ranges of top-level
elements. As an example:

cpp
for(auto &&[id, type]: entt::resolve<my_type>().base()) {
    // ...
}

Meta types are also used to construct actual instances of the underlying
type.
In particular, the construct member function accepts a variable number of
arguments and searches for a match. It then returns a meta_any object that may
or may not be initialized, depending on whether a suitable constructor was found
or not.
Conversion functions are not accessible instead. They are used internally by
meta_any and the meta objects when needed.

Meta types and meta objects in general contain much more than what was said.
Refer to the inline documentation for further details.

Tell me your name

For meta types, data and functions, users can also provide custom names:

cpp
entt::meta_factory<my_type>{}
    .type("type"_hs, "my_type")
    .data<&variable>("data"_hs, "variable")
    .func<&function>("func"_hs, "function");

The label provided should be a string literal. The library does not make
copies. It is up to the user to guarantee the lifetime of the name itself.
String identifiers are returned from the meta objects via the name function:

cpp
const char *name = entt::resolve<my_type>().name();

Since most of the time there is no need to differentiate the name from the
numeric identifier associated with a meta object, EnTT also offers a more
compact version of these functions:

cpp
entt::meta_factory<my_type>{}
    .type("my_type")
    .data<&variable>("variable")
    .func<&function>("function");

Again, the name provided should be a string literal. The string is then used
to generate a numeric identifier with the hashed_string class.
Despite support for names, there are no string-based lookup functions available.
That is, types (resolve) as well as data members (data) and function members
(func) are only searchable by numeric identifiers.

Container support

The runtime reflection system also supports containers of all types.
Moreover, containers does not necessarily mean those offered by the C++
standard library. In fact, user defined data structures can also work with the
meta system in many cases.

Containers are automatically detected based on some common traits.
For example, and not limited to, a sequence container must have a begin/end
pair that returns a forward iterator, while an associative container must also
provide a key_type member and a find function.
If a container is not recognized as such, it is still possible to provide an
adapter by specializing the template classes meta_sequence_container_traits
and meta_associative_container_traits. Similarly, users can inhibit the
detection of a type as a meta container by specializing the right traits class
but without providing any definition.

Standard library containers are generally exported as meta containers out of the
box (with the exception of std::string, which is not considered a sequence
container on purpose).
However, it is important to include the header file container.hpp to make
the right specializations available to the compiler when needed.
The same file also contains some examples for the users that are interested in
making their own containers available to the meta system.

For meta containers, the meta_any class returns properly initialized proxy
objects for ease of use. The following is a deliberately verbose example of how
users can access a proxy object for a sequence container:

cpp
std::vector<int> vec{1, 2, 3};
entt::meta_any any = entt::forward_as_meta(vec);

if(any.type().is_sequence_container()) {
    if(auto view = any.as_sequence_container(); view) {
        // ...
    }
}

Proxy object for associative containers are accessed in the same way by calling
as_associative_container instead.
It is not necessary to perform a double check actually. Instead, it is enough to
query the meta type or verify that the proxy object is valid. In fact, proxies
are contextually convertible to bool to check for validity. For example, invalid
proxies are returned when the wrapped object is not a container.
In all cases, users are not expected to reflect containers explicitly. It is
sufficient to assign a container for which a specialization of the traits
classes exists to a meta_any object to be able to get its proxy object.

The interface of the meta_sequence_container proxy object is the same for all
types of sequence containers, although the available features differ from case
to case. In particular:

  • The value_type member function returns the meta type of the elements.

  • The size member function returns the number of elements in the container as
    an unsigned integer value.

  • The resize member function allows resizing the wrapped container and returns
    true in case of success.
    For example, it is not possible to resize fixed size containers.

  • The clear member function allows clearing the wrapped container and returns
    true in case of success.
    For example, it is not possible to clear fixed size containers.

  • The reserve member function allows increasing the capacity of the wrapped
    container and returns true in case of success.
    For example, it is not possible to increase capacity of fixed size containers.

  • The begin and end member functions return opaque iterators that is used to
    iterate the container directly:

    cpp
    for(entt::meta_any element: view) {
        // ...
    }

    In all cases, given an underlying container of type C, the returned element
    contains an object of type C::value_type which therefore depends on the
    actual container.
    All meta iterators are input iterators and do not offer an indirection
    operator on purpose.

  • The insert member function is used to add elements to the container. It
    accepts a meta iterator and the element to insert:

    cpp
    auto last = view.end();
    // appends an integer to the container
    view.insert(last, 42);

    This function returns a meta iterator pointing to the inserted element and a
    boolean value to indicate whether the operation was successful or not. A call
    to insert may silently fail in case of fixed size containers or whether the
    arguments are not at least convertible to the required types.
    Since meta iterators are contextually convertible to bool, users can rely on
    them to know if the operation failed on the actual container or upstream, for
    example due to an argument conversion problem.

  • The erase member function is used to remove elements from the container. It
    accepts a meta iterator to the element to remove:

    cpp
    auto first = view.begin();
    // removes the first element from the container
    view.erase(first);

    This function returns a meta iterator following the last removed element and a
    boolean value to indicate whether the operation was successful or not. A call
    to erase may silently fail in case of fixed size containers.

  • The operator[] is used to access container elements. It accepts a single
    argument, the position of the element to return:

    cpp
    for(std::size_t pos{}, last = view.size(); pos < last; ++pos) {
        entt::meta_any value = view[pos];
        // ...
    }

    The function returns instances of meta_any that directly refer to the actual
    elements. Modifying the returned object directly modifies the element inside
    the container.
    Depending on the underlying sequence container, this operation may not be as
    efficient. For example, in the case of an std::list, a positional access
    translates to a linear visit of the list itself (probably not what the user
    expects).

Similarly, also the interface of the meta_associative_container proxy object
is the same for all types of associative containers. However, there are some
differences in behavior in the case of key-only containers. In particular:

  • The key_only member function returns true if the wrapped container is a
    key-only one.

  • The key_type member function returns the meta type of the keys.

  • The mapped_type member function returns an invalid meta type for key-only
    containers and the meta type of the mapped values for all other types of
    containers.

  • The value_type member function returns the meta type of the elements.
    For example, it returns the meta type of int for std::set while it
    returns the meta type of std::pair<const int, char> for
    std::map.

  • The size member function returns the number of elements in the container as
    an unsigned integer value.

  • The clear member function allows clearing the wrapped container and returns
    true in case of success.

  • The reserve member function allows increasing the capacity of the wrapped
    container and returns true in case of success.
    For example, it is not possible to increase capacity of standard maps.

  • The begin and end member functions return opaque iterators that are used
    to iterate the container directly:

    cpp
    for(std::pair<entt::meta_any, entt::meta_any> element: view) {
        // ...
    }

    In all cases, given an underlying container of type C, the returned element
    is a key-value pair where the key has type C::key_type and the value has
    type C::mapped_type. Since key-only containers do not have a mapped type,
    their value is nothing more than an invalid meta_any object.
    All meta iterators are input iterators and do not offer an indirection
    operator on purpose.

    While the accessed key is usually constant in the associative containers and
    is therefore returned by copy, the value (if any) is wrapped by an instance of
    meta_any that directly refers to the actual element. Modifying it directly
    modifies the element inside the container.

  • The insert member function is used to add elements to a container. It gets
    two arguments, the key and the value to insert:

    cpp
    auto last = view.end();
    // appends an integer to the container
    view.insert(last.handle(), 42, 'c');

    This function returns a boolean value to indicate whether the operation was
    successful or not. A call to insert may fail when the arguments are not at
    least convertible to the required types.

  • The erase member function is used to remove elements from a container. It
    gets a single argument, the key to remove:

    cpp
    view.erase(42);

    This function returns a boolean value to indicate whether the operation was
    successful or not. A call to erase may fail when the argument is not at
    least convertible to the required type.

  • The operator[] is used to access elements in a container. It gets a single
    argument, the key of the element to return:

    cpp
    entt::meta_any value = view[42];

    The function returns instances of meta_any that directly refer to the actual
    elements. Modifying the returned object directly modifies the element inside
    the container.

Container support is minimal but likely sufficient to satisfy all needs.

Pointer-like types

As with containers, it is also possible to tell to the meta system which types
are pointers. This makes it possible to dereference instances of meta_any,
thus obtaining light references to pointed objects that are also correctly
associated with their meta types.
To make the meta system recognize a type as pointer-like, users can specialize
the is_meta_pointer_like class. EnTT already exports the specializations for
some common classes. In particular:

  • All types of raw pointers.
  • std::unique_ptr and std::shared_ptr.
  • All classes that export a type member called is_meta_pointer_like:
    cpp
    struct smart_pointer {
        using is_meta_pointer_like = void;
        // ...
    };
    The actual type is irrelevant and will not be used in any way.

It is important to include the header file pointer.hpp to make these
specializations available to the compiler when needed.
The same file also contains many examples for the users that are interested in
making their own pointer-like types available to the meta system.

When a type is recognized as a pointer-like one by the meta system, it is
possible to dereference the instances of meta_any that contain these objects.
The following is a deliberately verbose example to show how to use this feature:

cpp
int value = 42;
// meta type equivalent to that of int *
entt::meta_any any{&value};

if(any.type().is_pointer_like()) {
    // meta type equivalent to that of int
    if(entt::meta_any ref = *any; ref) {
        // ...
    }
}

It is not necessary to perform a double check. Instead, it is enough to query
the meta type or verify that the returned object is valid. For example, invalid
instances are returned when the wrapped object is not a pointer-like type.
Dereferencing a pointer-like object returns an instance of meta_any which
refers to the pointed object. Modifying it means modifying the pointed object
directly (unless the returned element is const).

In general, dereferencing a pointer-like type boils down to a *ptr. However,
EnTT also supports classes that do not offer an operator*. In particular:

  • It is possible to exploit a solution based on ADL lookup by implementing a
    function (also a template one) named dereference_meta_pointer_like:

    cpp
    template<typename Type>
    Type & dereference_meta_pointer_like(const custom_pointer_type<Type> &ptr) {
        return ptr.deref();
    }
  • When not in control of the type's namespace, it is possible to inject into the
    entt namespace a specialization of the adl_meta_pointer_like class
    template to bypass the adl lookup as a whole:

    cpp
    template<typename Type>
    struct entt::adl_meta_pointer_like<custom_pointer_type<Type>> {
        static decltype(auto) dereference(const custom_pointer_type<Type> &ptr) {
            return ptr.deref();
        }
    };

In all other cases and when dereferencing a pointer works as expected regardless
of the pointed type, no user intervention is required.

Template information

Meta types also provide a minimal set of information about the nature of the
original type in case it is a class template.
By default, this works out of the box and requires no user action. However, it
is important to include the header file template.hpp to make this information
available to the compiler when needed.

Meta template information is easily found:

cpp
// this method returns true if the type is recognized as a class template specialization
if(auto type = entt::resolve<std::shared_ptr<my_type>>(); type.is_template_specialization()) {
    // meta type of the class template conveniently wrapped by entt::meta_class_template_tag
    auto class_type = type.template_type();

    // number of template arguments
    std::size_t arity = type.template_arity();

    // meta type of the i-th argument
    auto arg_type = type.template_arg(0u);
}

Typically, when template information for a type is required, what the library
provides is sufficient. However, there are some cases where a user may want more
details or a different set of information.
Consider the case of a class template that is meant to wrap function types:

cpp
template<typename>
struct function_type;

template<typename Ret, typename... Args>
struct function_type<Ret(Args...)> {};

In this case, rather than the function type, it might be useful to provide the
return type and unpacked arguments as if they were different template parameters
for the original class template.
To achieve this, users must enter the library internals and provide their own
specialization for the class template entt::meta_template_traits, such as:

cpp
template<typename Ret, typename... Args>
struct entt::meta_template_traits<function_type<Ret(Args...)>> {
    using class_type = meta_class_template_tag<function_type>;
    using args_type = type_list<Ret, Args...>;
};

The reflection system does not verify the accuracy of the information nor infer
a correspondence between real types and meta types.
Therefore, the specialization is used as is and the information it contains is
associated with the appropriate type when required.

Automatic conversions

In C++, there are a number of conversions allowed between arithmetic types that
make it convenient to work with this kind of data.
If this were to be translated into explicit registrations with the reflection
system, it would result in a long series of instructions such as the following:

cpp
entt::meta_factory<int>{}
    .conv<bool>()
    .conv<char>()
    // ...
    .conv<double>();

Repeated for each type eligible to undergo this type of conversions. This is
both error-prone and repetitive.
Similarly, the language allows users to silently convert unscoped enums to their
underlying types and offers what it takes to do the same for scoped enums. It
would result in the following if it were to be done explicitly:

cpp
entt::meta_factory<my_enum>{}
    .conv<std::underlying_type_t<my_enum>>();

Fortunately, all of this can also be avoided. EnTT offers implicit support for
these types of conversions:

cpp
entt::meta_any any{42};
any.allow_cast<double>();
double value = any.cast<double>();

With no need for registration, the conversion takes place automatically under
the hood. The same goes for a call to allow_cast involving a meta type:

cpp
entt::meta_type type = entt::resolve<int>();
entt::meta_any any{my_enum::a_value};
any.allow_cast(type);
int value = any.cast<int>();

This makes working with arithmetic types and scoped or unscoped enums as easy as
it is in C++.
It is still possible to set up conversion functions manually, and these are
always preferred over the automatic ones.

Implicitly generated default constructor

Creating objects of default constructible types through the reflection system
while not having to explicitly register the meta type or its default constructor
is also possible.
For example, in the case of primitive types like int or char, but not just
them.

For default constructible types only, default constructors are automatically
defined and associated with their meta types, whether they are explicitly or
implicitly generated.
Therefore, this is all needed to construct an integer from its meta type:

cpp
entt::resolve<int>().construct();

Where the meta type is, for example, the one returned from a meta container,
useful for building keys without knowing or having to register the actual types.

In all cases, when users register default constructors, they are preferred both
during searches and when the construct member function is invoked.

From void to any

Sometimes all a user has is an opaque pointer to an object of a known meta type.
It would be handy in this case to be able to construct a meta_any element from
it.
For this purpose, the meta_type class offers a from_void member function
designed to convert an opaque pointer into a meta_any:

cpp
entt::meta_any any = entt::resolve(id).from_void(element);

Unfortunately, it is not possible to do a check on the actual type. Therefore,
this call can be considered as a static cast with all its problems.
On the other hand, the ability to construct a meta_any from an opaque pointer
opens the door to some pretty interesting uses that are worth exploring.

Policies: the more, the less

Policies are a kind of compile-time directives that can be used when registering
reflection information.
Their purpose is to require slightly different behavior than the default in some
specific cases. For example, when reading a given data member, its value is
returned wrapped in a meta_any object which, by default, makes a copy of it.
For large objects or if the caller wants to access the original instance, this
behavior is not desirable. Policies are there to offer a solution to this and
other problems.

There are a few alternatives available at the moment:

  • The as-value policy, associated with the type entt::as_value_t.
    This is the default policy. In general, it should never be used explicitly,
    since it is implicitly selected if no other policy is specified.
    In this case, the return values of the functions as well as the properties
    exposed as data members are always returned by copy in a dedicated wrapper and
    therefore associated with their original meta types.

  • The as-void policy, associated with the type entt::as_void_t.
    Its purpose is to discard the return value of a meta object, whatever it is,
    thus making it appear as if its type were void:

    cpp
    entt::meta_factory<my_type>{}.func<&my_type::member_function, entt::as_void_t>("member"_hs);

    If the use with functions is obvious, perhaps less so is use with constructors
    and data members. In the first case, the returned wrapper is always empty even
    though the constructor is still invoked. In the second case, the property
    is not accessible for reading instead.

  • The as-ref and as-cref policies, associated with the types
    entt::as_ref_t and entt::as_cref_t.
    They allow to build wrappers that act as references to unmanaged objects.
    Accessing the object contained in the wrapper for which the reference was
    requested makes it possible to directly access the instance used to initialize
    the wrapper itself:

    cpp
    entt::meta_factory<my_type>{}.data<&my_type::data_member, entt::as_ref_t>("member"_hs);

    These policies work with constructors (for example, when objects are taken
    from an external container rather than created on demand), data members and
    functions in general.
    If on the one hand as_cref_t always forces the return type to be const,
    as_ref_t adapts to the constness of the passed object and to that of the
    return type if any.

  • The as-is policy, associated with the type entt::as_is_t.
    Useful for decoupling meta type creation code from calling code while still
    preserving the behavior of data members and member functions as defined:

    cpp
    entt::meta_factory<my_type>{}.func<&my_type::any_member, entt::as_is_t>("member"_hs);

    For data members or member functions that return a reference type, the value
    is returned by reference with the same constness. In all other cases, the
    value is returned by copy.

Some uses are rather trivial, but it is useful to note that there are some less
obvious corner cases that can in turn be solved with the use of policies.

Named constants and enums

As mentioned, the data member function is used to reflect constants of any
type.
This allows users to create meta types for enums that work exactly like any
other meta type built from a class. Similarly, arithmetic types are enriched
with constants of special meaning where required.
All values thus exported appear to users as if they were constant data members
of the reflected types. This avoids the need to export what is the difference
between enums and classes in C++ directly in the space of the reflected types.

Exposing constant values or elements from an enum is quite simple:

cpp
entt::meta_factory<my_enum>{}
    .data<my_enum::a_value>("a_value"_hs)
    .data<my_enum::another_value>("another_value"_hs);

entt::meta_factory<int>{}.data<2048>("max_int"_hs);

Accessing them is trivial as well. It is a matter of doing the following, as
with any other data member of a meta type:

cpp
auto value = entt::resolve<my_enum>().data("a_value"_hs).get({}).cast<my_enum>();
auto max = entt::resolve<int>().data("max_int"_hs).get({}).cast<int>();

All this happens behind the scenes without any allocation because of the small
object optimization performed by the meta_any class.

User defined data

Sometimes (for example, when it comes to creating an editor) it might be useful
to attach traits or arbitrary custom data to the meta objects created.

The main difference between them is that:

  • Traits are simple user-defined flags with much higher access performance. The
    library reserves up to 16 bits for traits, that is 16 flags for a bitmask or
    2^16 values otherwise.

  • Custom data are stored in a generic quick access area reserved for the user
    and which the library will never use under any circumstances.

In all cases, this support is currently available only for meta types, meta data
and meta functions.

Traits

User-defined traits are set via a meta factory:

cpp
entt::meta_factory<my_type>{}.traits(my_traits::required | my_traits::hidden);

In the example above, EnTT bitmask enum support is used, but any integral
value is fine, as long as it does not exceed 16 bits.
Traits can be assigned at different times. Subsequent calls to the traits
function do not reset previously set values. However, users must reset the
factory to the meta object of interest:

cpp
entt::meta_factory<my_type>{}
    .data<&my_type::data_member, entt::as_ref_t>("member"_hs)
    .traits(my_traits::internal);

Once created, all meta objects offer a member function named traits to get the
currently set value:

cpp
auto value = entt::resolve<my_type>().traits<my_traits>();

Note that the type is erased upon registration and must therefore be repeated
when traits are extracted, so as to allow the library to reconstruct them
correctly.

Custom data

Custom arbitrary data are set via a meta factory:

cpp
entt::meta_factory<my_type>{}.custom<type_data>("name");

The way to do this is by specifying the data type to the custom function and
passing the necessary arguments to construct it correctly.
It is not possible to assign custom data at different times. Therefore, multiple
calls to the custom function overwrite previous values. However, this value
can be read from meta objects and used to update existing data with a factory,
effectively updating them as needed.
Likewise, users can also set custom data on meta objects later if needed, as
long as the factory is reset to the meta object of interest:

cpp
entt::meta_factory<my_type>{}
    .func<&my_type::member_function>("member"_hs)
    .custom<function_data>("tooltip");

Once created, all meta objects offer a member function named custom to get the
currently set value as a reference or as a pointer to an element:

cpp
const type_data &value = entt::resolve<my_type>().custom();

Note that the returned object performs an extra check in debug before converting
to the requested type, so as to avoid subtle bugs.
Only in the case of conversion to a pointer is this check safe and such that a
null pointer is returned to inform the user of the failed attempt.

Unregister types

A type registered with the reflection system can also be unregistered. This
means unregistering all its data members, member functions, conversion functions
and so on. However, base classes are not unregistered as well, since they do not
necessarily depend on it.
Roughly speaking, unregistering a type means disconnecting all associated meta
objects from it and making its identifier no longer available:

cpp
entt::meta_reset<my_type>();

It is also possible to reset types by their unique identifiers:

cpp
entt::meta_reset("my_type"_hs);

Finally, there exists a non-template overload of the meta_reset function that
does not accept arguments and resets all meta types at once:

cpp
entt::meta_reset();

A type can be re-registered later with a completely different name and form.

Meta context

All meta types and their parts are created at runtime and stored in a default
context. This is obtained via a service locator as:

cpp
auto &&context = entt::locator<entt::meta_context>::value_or();

By itself, a context is an opaque object that the user can do little with.
However, users can replace an existing context with another at any time:

cpp
entt::meta_context other{};
auto &&context = entt::locator<entt::meta_context>::value_or();
std::swap(context, other);

This is useful for testing purposes or to define multiple context objects with
different meta types to use as appropriate.

If replacing the default context is not enough, EnTT also offers the ability
to use multiple and externally managed contexts with the runtime reflection
system.
For example, to create new meta types within a context other than the default
one, simply pass it as an argument to the meta_factory constructor:

cpp
entt::meta_ctx context{};
entt::meta_factory<my_type>{context}.type("reflected_type"_hs);

By doing so, the new meta type is not available in the default context but is
usable by passing around the new context when needed, such as when creating a
new meta_any object:

cpp
entt::meta_any any{context, std::in_place_type<my_type>};

Similarly, to search for meta types in a context other than the default one,
it is necessary to pass it to the resolve function:

cpp
entt::meta_type type = entt::resolve(context, "reflected_type"_hs)

More generally, when using externally managed contexts, it is always required to
provide the system with the context to use, at least at the entry point.
For example, once the meta_type instant is obtained, it is no longer necessary
to pass the context around as the meta type takes the information with it and
eventually propagates it to all its parts.
On the other hand, it is necessary to instruct the library on where meta types
are to be fetched when meta_anys and meta_handles are constructed, a factory
created or a meta type resolved.


Md Poly (`docs/md/poly.md`)

Crash Course: poly

Table of Contents

Introduction

Static polymorphism is a very powerful tool in C++, albeit sometimes cumbersome
to obtain.
This module aims to make it simple and easy to use.

The library allows to define concepts as interfaces to fulfill with concrete
classes without having to inherit from a common base.
Among others, this is one of the advantages of static polymorphism in general
and of a generic wrapper like that offered by the poly class template in
particular.
The result is an object to pass around as such and not through a reference or a
pointer, as it happens when it comes to working with dynamic polymorphism.

Since the poly class template makes use of entt::any internally, it also
supports most of its feature. For example, the possibility to create aliases to
existing and thus unmanaged objects. This allows users to exploit the static
polymorphism while maintaining ownership of objects.
Likewise, the poly class template also benefits from the small buffer
optimization offered by the entt::any class and therefore minimizes the number
of allocations, avoiding them altogether where possible.

Other libraries

There are some very interesting libraries regarding static polymorphism.
The ones that I like more are:

  • dyno: runtime polymorphism done right.
  • Poly:
    a class template that makes it easy to define a type-erasing polymorphic
    object wrapper.

The former is admittedly an experimental library, with many interesting ideas.
I have some doubts about the usefulness of some features in real world projects,
but perhaps my lack of experience comes into play here. In my opinion, its only
flaw is the API that I find slightly more cumbersome than other solutions.
The latter was undoubtedly a source of inspiration for this module. Although I
opted for different choices in the implementation of both the final API and some
features.

Either way, the authors are gurus of the C++ community, people I only have to
learn from.

Concept and implementation

The first thing to do to create a type-erasing polymorphic object wrapper (to
use the terminology introduced by Eric Niebler) is to define a concept that
types will have to adhere to.
For this purpose, the library offers a single class that supports both deduced
and fully defined interfaces. Although having interfaces deduced automatically
is convenient and allows users to write less code in most cases, it has some
limitations. It is therefore useful to be able to get around the deduction by
providing a custom definition for the static virtual table.

Once the interface is defined, a generic implementation is needed to fulfill the
concept itself.
Also in this case, the library allows customizations based on types or families
of types, so as to be able to go beyond the generic case where necessary.

Deduced interface

This is how a concept with a deduced interface is defined:

cpp
struct Drawable: entt::type_list<> {
    template<typename Base>
    struct type: Base {
        void draw() { this->template invoke<0>(*this); }
    };

    // ...
};

It is recognizable by the fact that it inherits from an empty type list.
Functions can also be const, accept any number of parameters and return a type
other than void:

cpp
struct Drawable: entt::type_list<> {
    template<typename Base>
    struct type: Base {
        bool draw(int pt) const { return this->template invoke<0>(*this, pt); }
    };

    // ...
};

In this case, all parameters are passed to invoke after the reference to
this and the return value is whatever the internal call returns.
As for invoke, this is a name that is injected into the concept through
Base, from which one must necessarily inherit. Since it is also a dependent
name, the this-> template form is unfortunately necessary due to the rules of
the language. However, there also exists an alternative that goes through an
external call:

cpp
struct Drawable: entt::type_list<> {
    template<typename Base>
    struct type: Base {
        void draw() const { entt::poly_call<0>(*this); }
    };

    // ...
};

Once the concept is defined, users must provide a generic implementation of it
in order to tell the system how any type can satisfy its requirements. This is
done via an alias template within the concept itself.
The index passed as a template parameter to either invoke or poly_call
refers to how this alias is defined.

Defined interface

A fully defined concept is no different to one for which the interface is
deduced, with the only difference that the list of types is not empty this time:

cpp
struct Drawable: entt::type_list<void()> {
    template<typename Base>
    struct type: Base {
        void draw() { entt::poly_call<0>(*this); }
    };

    // ...
};

Again, parameters and return values other than void are allowed. Also, the
function type must be const when the method to bind to it is const:

cpp
struct Drawable: entt::type_list<bool(int) const> {
    template<typename Base>
    struct type: Base {
        bool draw(int pt) const { return entt::poly_call<0>(*this, pt); }
    };

    // ...
};

Why should a user fully define a concept if the function types are the same as
the deduced ones?
In fact, this is the limitation that can be worked around by manually defining
the static virtual table.

When things are deduced, there is an implicit constraint.
If the concept exposes a member function called draw with function type
void(), a concept is satisfied:

  • Either by a class that exposes a member function with the same name and the
    same signature.

  • Or through a lambda that makes use of existing member functions from the
    interface itself.

In other words, it is not possible to make use of functions not belonging to the
interface, even if they are part of the types that fulfill the concept.
Similarly, it is not possible to deduce a function in the static virtual table
with a function type different from that of the associated member function in
the interface itself.

Explicitly defining a static virtual table suppresses the deduction step and
allows maximum flexibility when providing the implementation for a concept.

Fulfill a concept

The impl alias template of a concept is used to define how it is fulfilled:

cpp
struct Drawable: entt::type_list<> {
    // ...

    template<typename Type>
    using impl = entt::value_list<&Type::draw>;
};

In this case, it is stated that the draw method of a generic type is enough to
satisfy the requirements of the Drawable concept.
Both member functions and free functions are supported to fulfill concepts:

cpp
template<typename Type>
void print(Type &self) { self.print(); }

struct Drawable: entt::type_list<void()> {
    // ...

    template<typename Type>
    using impl = entt::value_list<&print<Type>>;
};

Likewise, as long as the parameter types and return type support conversions to
and from those of the function type referenced in the static virtual table, the
actual implementation may differ in its function type since it is erased
internally.
Moreover, the self parameter is not strictly required by the system and can be
left out for free functions if not required.

Refer to the inline documentation for more details.

Inheritance

Concept inheritance is straightforward due to how poly looks like in EnTT.
Therefore, it is quite easy to build hierarchies of concepts if necessary.
The only constraint is that all concepts in a hierarchy must belong to the same
family, that is, they must be either all deduced or all defined.

For a deduced concept, inheritance is achieved in a few steps:

cpp
struct DrawableAndErasable: entt::type_list<> {
    template<typename Base>
    struct type: Drawable::type<Base> {
        static constexpr auto base = Drawable::impl<Drawable::type<entt::poly_inspector>>::size;
        void erase() { entt::poly_call<base + 0>(*this); }
    };

    template<typename Type>
    using impl = entt::value_list_cat_t<
        Drawable::impl<Type>,
        entt::value_list<&Type::erase>
    >;
};

The static virtual table is empty and must remain so.
On the other hand, type no longer inherits from Base. Instead, it forwards
its template parameter to the type exposed by the base class. Internally, the
size of the static virtual table of the base class is used as an offset for
the local indexes.
Finally, by means of the value_list_cat_t utility, the implementation consists
in appending the new functions to the previous list.

As for a defined concept instead, the list of types is extended in a similar
way to what is shown for the implementation of the above concept.
To do this, it is useful to declare a function that allows to convert a
concept into its underlying type_list object:

cpp
template<typename... Type>
entt::type_list<Type...> as_type_list(const entt::type_list<Type...> &);

The definition is not strictly required, since the function is only used through
a decltype as it follows:

cpp
struct DrawableAndErasable: entt::type_list_cat_t<
    decltype(as_type_list(std::declval<Drawable>())),
    entt::type_list<void()>> {
    // ...
};

Similar to above, type_list_cat_t is used to concatenate the underlying static
virtual table with the new function types.
Everything else is the same as already shown instead.

Static polymorphism in the wild

Once the concept and implementation are defined, it is possible to use the
poly class template to wrap instances that meet the requirements:

cpp
using drawable = entt::poly<Drawable>;

struct circle {
    void draw() { /* ... */ }
};

struct square {
    void draw() { /* ... */ }
};

// ...

drawable instance{circle{}};
instance->draw();

instance = square{};
instance->draw();

This class offers a wide range of constructors, from the default one (which
returns an uninitialized poly object) to the copy and move constructors, as
well as the ability to create objects in-place.
Among others, there is also a constructor that allows users to wrap unmanaged
objects in a poly instance (either const or non-const ones):

cpp
circle shape;
drawable instance{std::in_place_type<circle &>, shape};

Similarly, it is possible to create non-owning copies of poly from an existing
object:

cpp
drawable other = instance.as_ref();

In both cases, although the interface of the poly object does not change, it
does not construct any element or take care of destroying the referenced
objects.

Note also how the underlying concept is accessed via a call to operator-> and
not directly as instance.draw().
This allows users to decouple the API of the wrapper from that of the concept.
Therefore, where instance.data() invokes the data member function of the
poly object, instance->data() maps directly to the functionality exposed by
the underlying concept.

Storage size and alignment requirement

Under the hood, the poly class template makes use of entt::any. Therefore,
it can take advantage of the possibility of defining at compile-time the size of
the storage suitable for the small buffer optimization as well as the alignment
requirements:

cpp
entt::basic_poly<Drawable, sizeof(double[4]), alignof(double[4])>

The default size is sizeof(double[2]), which seems like a good compromise
between a buffer that is too large and one unable to hold anything larger than
an integer. The alignment requirement is optional, and by default such that it
is the most stringent (the largest) for any object whose size is at most equal
to the one provided.
It is worth noting that providing a size of 0 (which is an accepted value in all
respects) will force the system to dynamically allocate the contained objects in
all cases.


Md Process (`docs/md/process.md`)

Crash Course: cooperative scheduler

Table of Contents

Introduction

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 other component
types.
EnTT offers minimal support to this paradigm by introducing a few classes used
to define and execute cooperative processes.

The process

A typical task inherits from the process class template. Derived classes also
specify what the intended type for elapsed times is.

A process should implement the following member functions whether needed (note
that it is not required to define a function unless the derived class wants to
override the default behavior):

  • void update(Delta, void *);

    This is invoked once per tick until a process is explicitly aborted or ends
    either with or without errors. 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 succeeded();

    This is invoked in case of success, immediately after an update and during the
    same tick.

  • void failed();

    This is invoked in case of errors, immediately after an update and during the
    same tick.

  • void aborted();

    This is invoked only if a process is explicitly aborted. There is no guarantee
    that it executes in the same tick. It depends solely on whether the process is
    aborted immediately or not.

A class can also change the state of a process by invoking succeed and fail,
as well as pause and unpause the process itself.
All these are public member functions made available to manage the life cycle of
a process easily.

Here is a minimal example for the sake of curiosity:

cpp
struct my_process: entt::process {
    using allocator_type = entt::process::allocator_type;
    using delta_type = entt::process::delta_type;

    my_process(const allocator_type &allocator, delta_type delay)
        : entt::process{allocator},
          remaining{delay}
    {}

    void update(delta_type delta, void *) {
        remaining -= std::min(remaining, delta);

        // ...

        if(!remaining) {
            succeed();
        }
    }

private:
    delta_type remaining;
};

Continuation

A process may be followed by other processes upon successful termination.
This pairing can be set up at creation time, keeping the processes conceptually
separate from each other while still combining them at runtime:

cpp
my_process process{};
process.then<my_other_process>();

This approach allows processes to be developed in isolation and combined to
define complex actions.
For example, a delayed operation where a parent process (such as a timer)
schedules a child process (the deferred task) once the time is over.

The then function also accepts lambdas, which are associated with a dedicated
process internally:

cpp
process.then([](entt::process &proc, std::uint32_t delta, void *data) {
    // ...
})

The lambda function is such that it accepts a reference to the process that
manages it (to be able to terminate it, pause it and so on), plus the usual
values also passed to the update function.

Shared process

All processes inherit from std::enable_shared_from_this to allow sharing with
the caller.
The returned smart pointer was created using the allocator associated with the
scheduler and therefore all its processes. This same allocator is available by
invoking get_allocator on the process itself.

As far as possible, sharing a process is not intended to allow the caller to
manage it. This could actually compromise the proper functioning of the
scheduler and the process itself.
Rather, the purpose is to allow the callers to save a valid reference to the
process, allowing them to intervene in its lifecycle through calls like pause
and the like.

The scheduler

A cooperative scheduler runs different processes and helps manage their life
cycles.

Each process is invoked once per tick. If it terminates, it is removed
automatically from the scheduler, and it is never invoked again. Otherwise,
it is a good candidate to run one more time the next tick.
A process can also have a child. In this case, the parent process is replaced
with its child when it terminates and only if it returns with success. In case
of errors, both the parent process and its child are discarded. This way, it is
easy to create a 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
entt::basic_scheduler<std::uint64_t> scheduler;

Otherwise, the scheduler alias is also available for the most common cases. It
uses std::uint32_t as a default type:

cpp
entt::scheduler scheduler{};

The class 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
entt::scheduler::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, invoke the attach function with a process
type and the arguments to use to construct it:

cpp
scheduler.attach<my_process>(_1000u);

The scheduler will also provide the process with its allocator as the first
argument.
In case of lambdas or functors, the required signature is the one already seen
for the then function of a process:

cpp
scheduler.attach([](entt::process &, std::uint32_t, void *){ /* ... */ });

In both cases, the newly created process is returned by reference and its then
member function is used to create chains of processes to run sequentially.
As a minimal example of use:

cpp
// schedules a task in the form of a lambda function
scheduler.attach([](entt::process &, std::uint32_t, void *) {
    // ...
})
// appends a child in the form of another lambda function
.then([](entt::process &, std::uint32_t, void *) {
    // ...
})
// appends a child in the form of a process class
.then<my_process>(1000u);

To update a scheduler and therefore 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 is 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();

The argument passed to the abort function indicates whether execution should
be stopped immediately or processes should be notified on the next tick.


Md Reference (`docs/md/reference.md`)

Similar projects

Table of Contents

Introduction

There are many projects similar to EnTT, both open source and not.
Some even borrowed some ideas from this library and expressed them in different
languages.
Others developed different architectures from scratch and therefore offer
alternative solutions with their pros and cons.

If you know of other similar projects out there, feel free to open an issue or a
PR, and I will be glad to add them to this page.
I hope the following lists can grow much more in the future.

Similar projects

Below an incomplete list of similar projects that I have come across so
far.
If some terms or designs are not clear, I recommend referring to the
ECS Back and Forth series for all the
details.

  • C:

    • destral_ecs: a single-file ECS based
      on sparse sets.
    • Diana: an ECS that uses sparse sets to
      keep track of entities in systems.
    • Flecs: a multithreaded archetype
      ECS based on semi-contiguous arrays rather than chunks.
    • lent: the Donald Trump of the ECS libraries.
  • C++:

    • decs: a chunk-based archetype ECS.
    • ecst: a multithreaded compile-time
      ECS that uses sparse sets to keep track of entities in systems.
    • EntityX: a bitset based ECS that
      uses a single large matrix of components indexed with entities.
    • Gaia-ECS: a chunk-based
      archetype ECS.
    • Polypropylene: a hybrid
      solution between an ECS and dynamic mixins.
  • C#

    • Arch: a simple, fast and unity entities
      inspired archetype ECS with optional multithreading.
    • Entitas: the ECS framework for
      C# and Unity, where reactive systems were invented.
    • Fennecs: the little archetype ECS that
      loves you back.
    • Friflo ECS: an archetype ECS
      with focus on performance and minimal GC allocations.
    • LeoECS: simple lightweight C# Entity
      Component System framework.
    • Massive ECS: sparse set based ECS
      featuring rollbacks.
    • Svelto.ECS: a very interesting
      platform-agnostic and table-based ECS framework.
  • Go:

    • gecs: a sparse sets based ECS inspired
      by EnTT.
  • Javascript:

    • @javelin/ecs:
      an archetype ECS in TypeScript.
    • ecsy: I have not had the time to
      investigate the underlying design of ecsy but it looks cool anyway.
  • Perl:

    • Game::Entities: a simple
      entity registry for ECS designs inspired by EnTT.
  • Raku:

    • Game::Entities: a simple
      entity registry for ECS designs inspired by EnTT.
  • Rust:

    • Shipyard: it borrows some ideas from
      EnTT and offers a sparse sets based ECS with grouping functionalities.
    • Sparsey: sparse set based ECS
      written in Rust.
    • Specs: a parallel ECS based mainly on
      hierarchical bitsets that allows different types of storage as needed.
  • Zig

    • zig-ecs: a zig-ification of EnTT.

Md Resource (`docs/md/resource.md`)

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 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.
Examples are loading everything on start, loading on request, predictive
loading, and so on.

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

The resource, the loader and the cache

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

cpp
struct my_resource { const int value; };

The loader is a callable type, the aim of which is to load a specific
resource:

cpp
struct my_loader final {
    using result_type = std::shared_ptr<my_resource>;

    result_type operator()(int value) const {
        // ...
        return std::make_shared<my_resource>(value);
    }
};

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).
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 (optionally) a loader:

cpp
using my_cache = entt::resource_cache<my_resource, my_loader>;

// ...

my_cache cache{};

The class is designed to create different caches for different resource types
and to manage each one independently in the most appropriate way.
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.

Resource handle

Resources are not returned directly to the caller. Instead, they are wrapped in
a resource handle, an instance of the entt::resource class template.
For those who know the flyweight design pattern already, that is 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
implementation mostly maps the interface of its standard counterpart and only
adds a few things on top of it.
However, the handle in EnTT 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.

Loaders

A loader is responsible for loading resources (quite obviously).
By default, it is just a callable object that 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.
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.
The loader is not required to return a resource handle. As long as return_type
is suitable for constructing a handle, that is 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).
In other terms, the loader should return shared pointers to the given resource
type. However, this is not 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
struct my_loader {
    using result_type = std::shared_ptr<my_resource>;

    struct from_disk_tag{};
    struct from_network_tag{};

    template<typename... Args>
    result_type operator()(from_disk_tag, Args&&... args) {
        // ...
        return std::make_shared<my_resource>(std::forward<Args>(args)...);
    }

    template<typename... Args>
    result_type operator()(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 to connect the dots.
It loads the resources, stores them aside and returns handles as needed:

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

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.
For this reason, it offers most of the functionalities a user would expect from
a map, such as empty or size and so on. Similarly, it is an iterable type
that also supports indexing by resource id:

cpp
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 other
functions (such as contains or erase).

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.
In particular, it does not 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
auto ret = cache.load("resource/id"_hs);

// true only if the resource was not already present
const bool loaded = ret.second;

// takes the resource handle pointed to by the returned iterator
entt::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, plain
numbers as well as non-class enum value are accepted.

It is worth mentioning that the iterators of a cache as well as its indexing
operators 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 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.
It is therefore recommended to verify handles validity with a check in debug
(for example, when loading) or an appropriate logic in retail.


Md Signal (`docs/md/signal.md`)

Crash Course: events, signals and everything in between

Table of Contents

Introduction

Signals are more often than not a core part of games and software architectures
in general.
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 this regard,
the std::function. As an example, it can be used to create delegates.
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 power of an std::function is not required or
if the price to pay for them is too high, EnTT offers a complete set of
lightweight classes to solve the same and many other problems.

Delegate

A delegate can be used as a general purpose invoker with no memory overhead for
free functions, lambdas and members provided along with an instance on which to
invoke them.
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. That said, it is most
likely even a better fit than an std::function in a lot of cases, so expect to
use it quite a lot anyway.

The interface is trivial. It offers a default constructor to create empty
delegates:

cpp
entt::delegate<int(int)> delegate{};

What is needed to create an instance is to specify the type of the function the
delegate accepts, that is the signature of the functions it models.
However, attempting to use an empty delegate by invoking its function call
operator results in undefined behavior or most likely a crash.

There exist a few overloads of the connect member function to initialize a
delegate:

cpp
int f(int i) { return i; }

struct my_struct {
    int f(const int &i) const { return i; }
};

// bind a free function to the delegate
delegate.connect<&f>();

// bind a member function to the delegate
my_struct instance;
delegate.connect<&my_struct::f>(instance);

The delegate class also accepts data members, if needed. In this case, the
function type of the delegate is such that the parameter list is empty and the
value of the data member is at least convertible to the return type.

Free functions having type equivalent to void(T &, args...) are accepted as
well. The first argument T & is considered a payload, and the function will
receive it back every time it is invoked. In other terms, this works just fine
with the above definition:

cpp
void g(const char &c, int i) { /* ... */ }
const char c = 'c';

delegate.connect<&g>(c);
delegate(42);

Function g is invoked with a reference to c and 42. However, the function
type of the delegate is still void(int). This is also the signature of its
function call operator.
Another interesting aspect of the delegate class is that it accepts functions
with a list of parameters that is shorter than that of its function type:

cpp
void g() { /* ... */ }
delegate.connect<&g>();
delegate(42);

Where the function type of the delegate is void(int) as above. It goes without
saying that the extra arguments are silently discarded internally. This is a
nice-to-have feature in a lot of cases, as an example when the delegate class
is used as a building block of a signal-slot system.
In fact, this filtering works both ways. The class tries to pass its first
count arguments first, then the last count. Watch out for conversion
rules if in doubt when connecting a listener!
Arbitrary functions that pull random arguments from the delegate list are not
supported instead. Other features were preferred, such as support for functions
with compatible argument lists although not equal to those of the delegate.

To create and initialize a delegate at once, there are a few specialized
constructors. Because of the rules of the language, the listener is provided by
means of the entt::connect_arg variable template:

cpp
entt::delegate<int(int)> func{entt::connect_arg<&f>};

Aside connect, a disconnect counterpart is not provided. Instead, there
exists a reset member function to use to clear a delegate.
To know if a delegate is empty, it can be used explicitly in every conditional
statement:

cpp
if(delegate) {
    // ...
}

Finally, to invoke a delegate, the function call operator is the way to go as
already shown in the examples above:

cpp
auto ret = delegate(42);

In all cases, listeners do not have to strictly follow the signature of the
delegate. As long as a listener can be invoked with the given arguments to yield
a result that is convertible to the given result type, everything works just
fine.

As a side note, members of classes may or may not be associated with instances.
If they are not, the first argument of the function type must be that of the
class on which the members operate, and an instance of this class must obviously
be passed when invoking the delegate:

cpp
entt::delegate<void(my_struct &, int)> delegate;
delegate.connect<&my_struct::f>();

my_struct instance;
delegate(instance, 42);

In this case, it is not possible to deduce the function type since the first
argument does not necessarily have to be a reference (for example, it can be a
pointer, as well as a const reference).
Therefore, the function type must be declared explicitly for unbound members.

Runtime arguments

The delegate class is meant to be used primarily with template arguments.
However, as a consequence of its design, it also offers minimal support for
runtime arguments.
When used like this, some features are not supported though. In particular:

  • Curried functions are not accepted.
  • Functions with an argument list that differs from that of the delegate are not
    supported.
  • Return type and types of arguments must coincide with those of the
    delegate and being at least convertible is not enough anymore.

Moreover, for a given function type Ret(Args...), the signature of the
functions connected at runtime must necessarily be Ret(const void *, Args...).

Runtime arguments can be passed both to the constructor of a delegate and to the
connect member function. An optional parameter is also accepted in both cases.
This argument is used to pass arbitrary user data back and forth as a
const void * upon invocation.
To connect a function to a delegate in the hard way:

cpp
int func(const void *ptr, int i) { return *static_cast<const int *>(ptr) * i; }
const int value = 42;

// use the constructor ...
entt::delegate delegate{&func, &value};

// ... or the connect member function
delegate.connect(&func, &value);

The type of the delegate is deduced from the function if possible. In this case,
since the first argument is an implementation detail, the deduced function type
is int(int).
Invoking a delegate built in this way follows the same rules as previously
explained.

Lambda support

In general, the delegate class does not fully support lambda functions in all
their nuances. The reason is pretty simple: a delegate is not a drop-in
replacement for an std::function. Instead, it tries to overcome the problems
with the latter.
That being said, non-capturing lambda functions are supported, even though some
features are not available in this case.

This is a logical consequence of the support for connecting functions at
runtime. Therefore, lambda functions undergo the same rules and
limitations.
In fact, since non-capturing lambda functions decay to pointers to functions,
they can be used with a delegate as if they were normal functions with
optional payload:

cpp
my_struct instance;

// use the constructor ...
entt::delegate delegate{+[](const void *ptr, int value) {
    return static_cast<const my_struct *>(ptr)->f(value);
}, &instance};

// ... or the connect member function
delegate.connect([](const void *ptr, int value) {
    return static_cast<const my_struct *>(ptr)->f(value);
}, &instance);

As above, the first parameter (const void *) is not part of the function type
of the delegate and is used to dispatch arbitrary user data back and forth. In
other terms, the function type of the delegate above is int(int).

Raw access

While not recommended, a delegate also allows direct access to the stored
callable function target and underlying data, if any.
This makes it possible to bypass the behavior of the delegate itself and force
calls on different instances:

cpp
my_struct other;
delegate.target(&other, 42);

It goes without saying that this type of approach is very risky, especially
since there is no way of knowing whether the contained function was originally a
member function of some class, a free function or a lambda.
Another possible (and meaningful) use of this feature is that of identifying a
particular delegate through its descriptive traits instead.

Signals

Signal handlers work with references to classes, function pointers, and pointers
to members. 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 should not be affected that
much by the presence of such a signal handler.
Signals make use of delegates internally, and therefore they undergo the same
rules and offer similar functionalities. It may be a good idea to consult the
documentation of the delegate class for further information.

A signal handler is 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 the
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. If a collector is supplied to
the signal when something is published, all the values returned by its listeners
are literally collected and used later by the caller. Otherwise, the class
works just like a plain signal that emits events from time to time.
To create instances of signal handlers it is sufficient to provide the type of
function to which they refer:

cpp
entt::sigh<void(int, char)> signal;

Signals offer all the basic functionalities required to know how many listeners
they contain (size) or if they contain at least a listener (empty), as well
as a function to use to swap 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 listener {
    void bar(const int &, char) { /* ... */ }
};

// ...

entt::sink sink{signal};
listener instance;

sink.connect<&foo>();
sink.connect<&listener::bar>(instance);

// ...

// disconnects a free function
sink.disconnect<&foo>();

// disconnect a member function of an instance
sink.disconnect<&listener::bar>(instance);

// disconnect all member functions of an instance, if any
sink.disconnect(&instance);

// discards all listeners at once
sink.disconnect();

As shown above, listeners do not have to strictly follow the signature of the
signal. As long as a listener can be invoked with the given arguments to yield a
result that is convertible to the given return type, everything works just
fine.
In all cases, the connect member function returns by default a connection
object to be used as an alternative to break a connection by means of its
release member function.
A scoped_connection can also be created from a connection. In this case, the
link is broken automatically as soon as the object goes out of scope.

Once listeners are attached (or even if there are no listeners at all), events
and data in general are published through a signal by means of the publish
member function:

cpp
signal.publish(42, 'c');

To collect data, the collect member function is used instead:

cpp
int f() { return 0; }
int g() { return 1; }

// ...

entt::sigh<int()> signal;
entt::sink sink{signal};

sink.connect<&f>();
sink.connect<&g>();

std::vector<int> vec{};
signal.collect([&vec](int value) { vec.push_back(value); });

assert(vec[0] == 0);
assert(vec[1] == 1);

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 can
optionally return a boolean value that is true to stop collecting data, false
otherwise. This way one can avoid calling all the listeners in case it is not
necessary.
Functors can also be used in place of a lambda. Since the collector is copied
when invoking the collect member function, std::ref is the way to go in this
case:

cpp
struct my_collector {
    std::vector<int> vec{};

    bool operator()(int v) {
        vec.push_back(v);
        return true;
    }
};

// ...

my_collector collector;
signal.collect(std::ref(collector));

Event dispatcher

The event dispatcher class allows users to trigger immediate events or to queue
and publish them all together later:

cpp
// define a general purpose dispatcher
entt::dispatcher dispatcher{};

This class lazily instantiates its queues. Therefore, it is not necessary to
announce the event types in advance.

Connect, disconnect, publish

Listeners registered with a dispatcher are mainly of two types: free and member
functions. Lambdas as template functions are also accepted and belong to the
first group.
In all cases, a listener accepts an argument of type Event & for any type
of event, regardless of the return value.

Listeners are linked directly via connect to a sink object:

cpp
struct an_event { int value; };
struct another_event {};

void on_event(const an_event &event) { /* ... */ }

struct listener {
    // Member function listener
    void on_event(const another_event &) { /* ... */ }
};

// ...

// free function listener
dispatcher.sink<an_event>().connect<&on_event>();

listener listener;
// member function listener
dispatcher.sink<another_event>().connect<&listener::on_event>(listener);

Note that connecting listeners within event handlers can result in undefined
behavior.
The disconnect member function is used to remove one listener at a time or all
of them at once:

cpp
// disconnects a free function
dispatcher.sink<an_event>().disconnect<&on_event>();

// disconnect a member function of an instance
dispatcher.sink<another_event>().disconnect<&listener::on_event>(listener);

// disconnect all member functions of an instance, if any
dispatcher.sink<another_event>().disconnect(&listener);

The trigger member function serves the purpose of sending an immediate event
to all the listeners registered so far:

cpp
dispatcher.trigger(an_event{42});
dispatcher.trigger(another_event{});

Listeners are invoked immediately, order of execution is not 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
helps to maintain control over the moment they are sent to listeners:

cpp
dispatcher.enqueue<an_event>(42);
dispatcher.enqueue(another_event{});

Events are stored aside until the update member function is invoked:

cpp
// emits all the events of the given type at once
dispatcher.update<an_event>();

// 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.

Named queues

All queues within a dispatcher are associated by default with an event type and
then retrieved from it.
However, it is possible to create queues with different names (and therefore
also multiple queues for a single type). In fact, more or less all functions
also take an additional parameter. As an example:

cpp
dispatcher.sink<an_event>("custom"_hs).connect<&listener::receive>(listener);

In this case, the term name is misused as these are actual numeric identifiers
of type id_type.
An exception to this rule is the enqueue function. There is no additional
parameter for it but rather a different function:

cpp
dispatcher.enqueue_hint<an_event>("custom"_hs, 42);

This is mainly due to the template argument deduction rules, and there is no
real (elegant) way to avoid it.

Event emitter

A general purpose event emitter thought mainly for those cases where it comes to
working with asynchronous stuff.
Originally designed to fit the requirements of
uvw (a wrapper for libuv written in
modern C++), it was adapted later to be included in this library.

To create an emitter type, derived classes must inherit from the base as:

cpp
struct my_emitter: emitter<my_emitter> {
    // ...
}

Handlers for the different events are created internally on the fly. It is not
required to specify in advance the full list of accepted events.
Moreover, whenever an event is published, an emitter also passes a reference
to itself to its listeners.

To create new instances of an emitter, no arguments are required:

cpp
my_emitter emitter{};

Listeners are movable and callable objects (free functions, lambdas, functors,
std::functions, whatever) whose function type is compatible with:

cpp
void(Type &, my_emitter &)

Where Type is the type of event they want to receive.
To attach a listener to an emitter, there exists the on member function:

cpp
emitter.on<my_event>([](const my_event &event, my_emitter &emitter) {
    // ...
});

Similarly, the reset member function is used to disconnect listeners given a
type while clear is used to disconnect all listeners at once:

cpp
// resets the listener for my_event
emitter.erase<my_event>();

// resets all listeners
emitter.clear()

To send an event to the listener registered on a given type, the publish
function is the way to go:

cpp
struct my_event { int i; };

// ...

emitter.publish(my_event{42});

Finally, the empty member function tests if there exists at least a listener
registered with the event emitter while contains is used to check if a given
event type is associated with a valid listener:

cpp
if(emitter.contains<my_event>()) {
    // ...
}

This class introduces a nice-to-have model based on events and listeners.
More in general, it is a handy tool when the derived classes wrap asynchronous
operations, but it is not limited to such uses.


Md Stl (`docs/md/stl.md`)

Crash Course: User provided STL

Table of Contents

Introduction

EnTT internally decouples the standard template library from the std
namespace. It does this by introducing the internal stl namespace into which
the necessary tools are imported from std.
This import process is also configurable, and the end user can replace all or
some of the standard template library includes by providing their own
implementations.

The required tools

The list of what is imported from std is not fixed and stable over time, but
changes as the library evolves. At any time, users can easily know what is
required and what is not.
The easiest way to avoid errors is to provide everything a standard header
provides. However, it's also a good idea to look at its counterpart in the stl
folder and implement only the tools actually used by EnTT.
There's no guarantee that these won't change over time. Therefore, the first
approach is the safest way to avoid surprises.

STL injection

Includes from the standard template library can be injected into EnTT in
bulk or individually. As long as the API and guarantees remain the same, there
will be no problem using different sources or mixing different
implementations.
However, there are two fundamental requirements for the library to be able to
find the user files and use them correctly.

Discoverability

To begin with, the files to inject must be discoverable via __has_include in
the entt/ext/stl/ path, and have the extension .hpp.
For example, to replace ``, users need to provide a resolvable file such as
<entt/ext/stl/bit.hpp>.

This is fairly simple to do on the command line and can be easily automated with
tools like CMake.
The EnTT test suite provides an example of how to proceed, also designed to
avoid future regressions.

Importability

Having all the required files is not enough, since EnTT does not know the
namespace in which the user defined their tools. Therefore, anyone providing the
new headers must also import all the tools into the entt::stl namespace.
Importing from outside into this namespace is accepted and, in a sense, expected
by the library itself, unlike what happens with the standard C++ library.

The easiest solution is to include a custom implementation and import it into
the required namespace via the files indicated.
An using namespace my_stl; directive should get the job done, but users can
also import the tools one by one if they prefer to.

Final note

EnTT is designed to work with the API, guarantees, and pros and cons of the
standard template library.
The flexibility afforded by the ability to import a custom implementation does
not compromise this requirement. Therefore, in the event of an incompatible API
(not different, but incompatible) or broken requirements, there is no
guarantee that EnTT will:

  • Compile correctly
  • Perform correctly

Obviously, any errors due to incorrect implementations or broken guarantees are
not attributable to the library, and no follow-up will be given to any issues
arising from these types of problems.


--- METRICS ---