ECSTASY
All in the name
Loading...
Searching...
No Matches
Glossary

This page will help you understand the various concepts used in ecstasy (most are common to other ECS). Do not hesitate to create an issue if a definition is still complicated or if a complex term is missing.

If you're not familiar with the concept of ECS, go see this quite complete general FAQ here.

The following definitions may be redundant for common notions but will also explain ECSTASY specific concepts.

Entity

An entity represents a distinct object or item in the registry. It is an abstract, unique identifier that groups together components to define its characteristics and behavior. Entities in an ECS can be seen as a link between multiple components.

References: Entity, RegistryEntity, Entities

Component

A component is a modular and reusable piece of data that encapsulates a specific aspect of an entity's state or behavior. Components are combined to compose the overall properties and functionalities of an entity.

It is usually a simple struct for example:

struct Position {
float x;
float y;
};
Note
There is almost no constraint on component types in ECSTASY, so you can use external library classes as components.

Storage

A storage is a component container. It is used to store and manage components and their associated entity identifier. It organizes components for efficient access and iteration during system execution, facilitating quick retrieval and manipulation of entity data.

Note
Ecstasy already implements some storage types but you can also create your own.

References: IStorage, ecstasy::AStorage, MapStorage, MarkerStorage, VectorStorage, IsStorage, getStorageType, Registry.addStorage()

System

A system is a logic unit responsible for performing actions on the registry. It usually operate on entities with specific sets of components. Systems operate independently and collectively define the overall behavior of the ECS. They encapsulate the logic that acts on entities.

In fact it is a function called with an instance of the registry:

struct Movement : public ecstasy::ISystem {
void run(ecstasy::Registry &registry) override final
{
for (auto [position, velocity] : registry.query<Position, Velocity>()) {
position.x += velocity.x;
position.y += velocity.y;
}
}
};
System interface, base class of all systems.
Definition ISystem.hpp:28
virtual void run(Registry &registry)=0
Run the system.
Base of an ECS architecture.
Definition Registry.hpp:82

References: ISystem, Registry.addSystem(), Registry.runSystem(), Registry.runSystems()

Resource

A resource is a shared and globally accessible piece of data that can be used by systems or entities in the ECS. Resources typically represent data that is not tied to a specific entity but is needed by various parts of the registry, such as configuration settings or global parameters.

Note
In ecstasy, the Entities class is a resource present by default in the registry.

A simple use case would be a Timer resource to improve our previous Movement system:

class Timer : public ecstasy::IResource {
public:
explicit Timer() noexcept : _lastReset(std::chrono::steady_clock::now()) {}
void reset() noexcept { this->_lastReset = std::chrono::steady_clock::now(); }
double elapsed() const noexcept
{
std::chrono::steady_clock::time_point now(std::chrono::steady_clock::now());
std::chrono::duration<float, std::ratio<1>> elapsed = now - this->_lastReset;
return elapsed.count();
}
private:
std::chrono::steady_clock::time_point _lastReset;
};
struct Movement : public ecstasy::ISystem {
void run(ecstasy::Registry &registry) override final
{
Timer &timer = registry.getResource<Timer>()
float seconds = timer.elapsed();
timer.reset();
for (auto [position, velocity] : registry.query<Position, Velocity>()) {
position.x += velocity.x * seconds;
position.y += velocity.y * seconds;
}
}
};
Base class of all registry resources.
Definition IResource.hpp:33

References: IResource, registry.addResource(), registry.getResource(), Entities

Registry

The registry is a centralized database or manager responsible for creating, managing, and tracking entities within the ECS. It maintains the relationships between entities and their components and provides essential functionality for entity lifecycle management. Sometimes called a world in other ECS frameworks.

The registry contains:

  • Systems
  • Resources (therefore entities)
  • Component storages

It is the link between the different data, the orchestrator of the application.

References: Registry

Query

A query in the context of this ECS refers to the process of selecting entities based on specific criteria or conditions. It allows developers to identify a subset of entities that possess certain components or meet certain requirements.

Ecstasy provides a SQL like way of querying your entities which means more concepts to define. You can follow this tutorial to use them for more informations but you should first understand the associated concepts explained below.

Queryable

Queryable is a ecstasy concept (in the meaning of c++20 template concepts) on which the ecstasy query can operate.

They must:

  • Define the type of the queried value
  • Allow to check whether the value is existing at a given index (entity)
  • Allow to fetch the value at a given index (entity)

The easiest example are the component storages:

  • The queried value type is the component
  • They provide a way to test if an entity have the component
  • They allows to access it.

But it is also true for the Entities resource. The goal of the queryable is to allow performing queries on various data types, not limiting them to only Storages but any type matching the concept.

Example concept definition:

template <typename Q>
concept Queryable = requires(Q &queryable, Q const &cqueryable, std::size_t index)
{
typename Q::QueryData;
{ cqueryable.getMask() } -> std::same_as<util::BitSet const &>;
{ queryable.getQueryData(index) } -> std::same_as<typename Q::QueryData>;
};

In fact there is multiple Queryable sub concepts such as QueryableObject (example above), ConstQueryableObject and QueryableWrapper. A Queryable must validate at least one of the said concepts.

References: Queryable, QueryableObject, ConstQueryableObject, QueryableWrapper

Batch Query

A batch query is a multi threaded query. Instead of iterating over the matching entities yourself in the current thread you can use the splitThreads method. It split the iteration over multiple threads of a custom batch size.

In the following example, if you have 167 entities with a position and velocity components, it will starts 4 threads (4 batches) and execute the function given in parameter:

  • Thread 1: 1-50
  • Thread 2: 51-100
  • Thread 3: 101-150
  • Thread 4: 151-167
// Will make one thread for every 50 matching entities
registry.query<Position, Velocity>().splitThreads(50, [](auto components) {
auto [position, velocity] = components;
position.x += velocity.x;
position.y += velocity.y;
});

Modifier

Modifiers is another ecstasy concept allowing to perform more complex queries in the registry. They must also match the queryable concept but they are in fact queryable wrappers.

If you are familiar with the decorators concepts in Python, Java, or any other language you can see the modifiers as a Queryable decorator:

  • You can set a modifier on any queryable, like you can apply a decorator on any function
  • A modifier is also a queryable, like decorators are the original function with some modifications
  • Therefore you can chain the modifiers, like you can apply decorators over decorators and so on.
Warning
There is in fact one big difference when using modifiers: They change the queried value type and you must look at their documentation to knows what is the modified type.

Here is the list of the implemented modifiers with their return data type. Note that nothing prevent you to implement other modifiers on your personal projects or by submitting an issue to ecstasy.

As always, I think examples are more readable:

// Match all entities with: Position and Velocity
// Returns: position and velocity components. If existing, the density component too (using std::optional).
registry.query<Position, Velocity, Maybe<Density>>()
// Match all entities with: Position or Positions
// Returns: a tuple of 2 std::optional, one for each component. At least one of them will be defined.
registry.query<Or<Position, Positions>>()
// Warning: this doesn't make sense, just to illustrate nested modifiers
// Match all entities with: Position or maybe Positions
// Returns: a tuple of 2 std::optional, one for each component. At least one of them will be defined.
registry.query<Or<Position, Maybe<Positions>>>()

Follow this tutorial to learn how to use them in details.

Concept definition:

template <typename M>
concept Modifier = requires(M &modifier)
{
requires Queryable<M>;
requires std::derived_from<M, ecstasy::query::modifier::Modifier>;
typename M::Operands;
};

References: Modifier, Maybe , Not, Or, Xor, And

Condition

Condition is (yet) another ecstasy concept allowing to perform conditions on the fetched data instead of on the presence of data. They are not queryables and are evaluated when iterating on the query.

When you are using queryables or modifiers the query performs verification on the presence of the component and not on the component state. The conditions however operates on the component state. This is just syntactic sugar to remove one small line in your code.

Warning
The condition cannot works if the evaluated type is not in the query queryables.

Ecstasy provides the following conditions:

And here is a little example where the two loops does the same thing:

for (auto [life] : registry.query<Life>()) {
if (life.value != 0)
continue;
// Entity deletion code
}
for (auto [life] : registry.select<Life>().where<ecstasy::EqualTo<&Life::value, 0>>()) {
// Entity deletion code
}
Namespace containing all symbols specific to ecstasy.
Definition ecstasy.hpp:30

Follow this tutorial to learn how to use them in details.

References: QCondition, EqualTo, NotEqualTo, Greater, GreaterEqual, Less, LessEqual

Thread

Lockable

Lockable is a concept following the c++ named requirement BasicLockable. To resume, it defines a type having the methods lock and unlock. That's it.

template <typename L>
concept Lockable = requires(L &lockable) {
// clang-format off
{ lockable.lock() } -> std::same_as<void>;
{ lockable.unlock() } -> std::same_as<void>;
// clang-format on
};

References: Lockable, LockableView

Serialization

Serializer actions

Export

Export* methods allows to save the serialized variables into bytes. It can be loaded later with Import* methods.

Import

Import* methods allows to load a previously serialized data (with Export* methods) into the serializer instance, to deserialize it into variables.

Variable actions

Serialize: Save

Serialize a variable and append it to the serializer stream.

Deserialize: Update

Read the serialized form of a variable and apply it to an existing variable.

Deserialize: Load

Instantiate a new variable from its serialized format.