signals on tags
This commit is contained in:
130
README.md
130
README.md
@@ -15,8 +15,9 @@
|
||||
* [Pay per use](#pay-per-use)
|
||||
* [Vademecum](#vademecum)
|
||||
* [The Registry, the Entity and the Component](#the-registry-the-entity-and-the-component)
|
||||
* [Observe changes](#observe-changes)
|
||||
* [Single instance components](#single-instance-components)
|
||||
* [Observe changes](#observe-changes)
|
||||
* [Who let the tags out?](#who-let-the-tags-out?)
|
||||
* [Runtime components](#runtime-components)
|
||||
* [A journey through a plugin](#a-journey-through-a-plugin)
|
||||
* [Sorting: is it possible?](#sorting-is-it-possible)
|
||||
@@ -529,54 +530,6 @@ std::tuple<Position &, Velocity &> tup = registry.get<Position, Velocity>(entity
|
||||
The `get` member function template gives direct access to the component of an
|
||||
entity stored in the underlying data structures of the registry.
|
||||
|
||||
### Observe changes
|
||||
|
||||
Because of how the registry works internally, it stores a couple of signal
|
||||
handlers for each pool in order to notify some of its data structures on the
|
||||
construction and destruction of components.<br/>
|
||||
These signal handlers are also exposed and made available to users. This is the
|
||||
basic brick to build fancy things like blueprints and reactive systems.
|
||||
|
||||
To get a sink to be used to connect and disconnect listeners so as to be
|
||||
notified on the creation of a component, use the `construction` member function:
|
||||
|
||||
```cpp
|
||||
// connects a free function
|
||||
registry.construction<Position>().connect<&MyFreeFunction>();
|
||||
|
||||
// connects a member function
|
||||
registry.construction<Position>().connect<MyClass, &MyClass::member>(&instance);
|
||||
|
||||
// disconnects a free function
|
||||
registry.construction<Position>().disconnect<&MyFreeFunction>();
|
||||
|
||||
// disconnects a member function
|
||||
registry.construction<Position>().disconnect<MyClass, &MyClass::member>(&instance);
|
||||
```
|
||||
|
||||
To be notified when components are destroyed, use the `destruction` member
|
||||
function instead.
|
||||
|
||||
The function type of a listener is the same in both cases:
|
||||
|
||||
```cpp
|
||||
void(Registry<Entity> &, Entity);
|
||||
```
|
||||
|
||||
In other terms, a listener is provided with the registry that triggered the
|
||||
notification and the entity affected by the change. Note also that:
|
||||
|
||||
* Listeners are invoked **after** components have been assigned to entities.
|
||||
* Listeners are invoked **before** components have been removed from entities.
|
||||
* The order of invocation of the listeners isn't guaranteed in any case.
|
||||
|
||||
There are also some limitations on what a listener can and cannot do. In
|
||||
particular, connecting and disconnecting other functions from within the body of
|
||||
a listener should be avoided. It can result in undefined behavior.<br/>
|
||||
In general, events and therefore listeners must not be used as replacements for
|
||||
systems. They should not contain much logic and interactions with a registry
|
||||
should be kept to a minimum, if possible.
|
||||
|
||||
### Single instance components
|
||||
|
||||
In those cases where all what is needed is a single instance component, tags are
|
||||
@@ -652,6 +605,85 @@ auto player = registry.attachee<PlayingCharacter>();
|
||||
Note that iterating tags isn't possible for obvious reasons. Tags give direct
|
||||
access to single entities and nothing more.
|
||||
|
||||
### Observe changes
|
||||
|
||||
Because of how the registry works internally, it stores a couple of signal
|
||||
handlers for each pool in order to notify some of its data structures on the
|
||||
construction and destruction of components.<br/>
|
||||
These signal handlers are also exposed and made available to users. This is the
|
||||
basic brick to build fancy things like blueprints and reactive systems.
|
||||
|
||||
To get a sink to be used to connect and disconnect listeners so as to be
|
||||
notified on the creation of a component, use the `construction` member function:
|
||||
|
||||
```cpp
|
||||
// connects a free function
|
||||
registry.construction<Position>().connect<&MyFreeFunction>();
|
||||
|
||||
// connects a member function
|
||||
registry.construction<Position>().connect<MyClass, &MyClass::member>(&instance);
|
||||
|
||||
// disconnects a free function
|
||||
registry.construction<Position>().disconnect<&MyFreeFunction>();
|
||||
|
||||
// disconnects a member function
|
||||
registry.construction<Position>().disconnect<MyClass, &MyClass::member>(&instance);
|
||||
```
|
||||
|
||||
To be notified when components are destroyed, use the `destruction` member
|
||||
function instead.
|
||||
|
||||
The function type of a listener is the same in both cases:
|
||||
|
||||
```cpp
|
||||
void(Registry<Entity> &, Entity);
|
||||
```
|
||||
|
||||
In other terms, a listener is provided with the registry that triggered the
|
||||
notification and the entity affected by the change. Note also that:
|
||||
|
||||
* Listeners are invoked **after** components have been assigned to entities.
|
||||
* Listeners are invoked **before** components have been removed from entities.
|
||||
* The order of invocation of the listeners isn't guaranteed in any case.
|
||||
|
||||
There are also some limitations on what a listener can and cannot do. In
|
||||
particular:
|
||||
|
||||
* Connecting and disconnecting other functions from within the body of a
|
||||
listener should be avoided. It can lead to undefined behavior.
|
||||
* Assigning and removing components and tags 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. This type of listeners is intended to
|
||||
provide users with an easy way to perform cleanup and nothing more.
|
||||
|
||||
In general, events and therefore listeners must not be used as replacements for
|
||||
systems. They should not contain much logic and interactions with a registry
|
||||
should be kept to a minimum, if possible. Note also that the greater the number
|
||||
of listeners, the greater the performance hit when components are created or
|
||||
destroyed.
|
||||
|
||||
#### Who let the tags out?
|
||||
|
||||
As an extension, signals are also provided with tags. Although they are not
|
||||
strictly required internally, it makes sense that an user expects signal support
|
||||
even when it comes to tags actually.<br/>
|
||||
Signals for tags undergo exactly the same requirements of those introduced for
|
||||
components. Also the function type for a listener is the same and it's invoked
|
||||
with the same guarantees discussed above.
|
||||
|
||||
To get the sinks for a tag just use `entt::tag_type_t` to disambiguate overloads
|
||||
of member functions as in the following example:
|
||||
|
||||
```cpp
|
||||
registry.construction<MyTag>(entt::tag_type_t{}).connect<&MyFreeFunction>();
|
||||
registry.destruction<MyTag>(entt::tag_type_t{}).connect<MyClass, &MyClass::member>(&instance);
|
||||
```
|
||||
|
||||
Listeners for tags and components are managed separately and do not influence
|
||||
each other in any case. Therefore, note that the greater the number of listeners
|
||||
for a type, the greater the performance hit when a tag of the given type is
|
||||
created or destroyed.
|
||||
|
||||
### Runtime components
|
||||
|
||||
Defining components at runtime is useful to support plugins and mods in general.
|
||||
|
||||
2
TODO
2
TODO
@@ -8,5 +8,7 @@
|
||||
* does it worth it to add an optional functor to the member functions of snapshot so as to filter out instances and entities?
|
||||
* ease the assignment of tags as string (use a template class with a non-type template parameter behind the scene)
|
||||
* dictionary based dependency class (templates copied over) + prefabs (shared state/copy-on-write)
|
||||
* update benchmarks, destroy is probably slower now because of signals on components/tags
|
||||
* "singleton mode" for tags (see #66)
|
||||
* introduce a fast destroy that doesn't check for components or tags (it does it only in debug) and discards the entity immediately (use it with snapshot orphans)
|
||||
* AOB
|
||||
|
||||
@@ -38,24 +38,19 @@ class Registry {
|
||||
using tag_family = Family<struct InternalRegistryTagFamily>;
|
||||
using component_family = Family<struct InternalRegistryComponentFamily>;
|
||||
using handler_family = Family<struct InternalRegistryHandlerFamily>;
|
||||
using signal_type = SigH<void(Registry &, Entity)>;
|
||||
using traits_type = entt_traits<Entity>;
|
||||
|
||||
template<typename... Component>
|
||||
static void creating(Registry ®istry, Entity entity) {
|
||||
if(registry.has<Component...>(entity)) {
|
||||
const auto htype = handler_family::type<Component...>();
|
||||
registry.handlers[htype]->construct(entity);
|
||||
}
|
||||
const auto ttype = handler_family::type<Component...>();
|
||||
return registry.has<Component...>(entity) ? registry.handlers[ttype]->construct(entity) : void();
|
||||
}
|
||||
|
||||
template<typename... Component>
|
||||
static void destroying(Registry ®istry, Entity entity) {
|
||||
const auto htype = handler_family::type<Component...>();
|
||||
auto &handler = registry.handlers[htype];
|
||||
|
||||
if(handler->has(entity)) {
|
||||
handler->destroy(entity);
|
||||
}
|
||||
auto &handler = registry.handlers[handler_family::type<Component...>()];
|
||||
return handler->has(entity) ? handler->destroy(entity) : void();
|
||||
}
|
||||
|
||||
struct Attachee {
|
||||
@@ -76,7 +71,19 @@ class Registry {
|
||||
};
|
||||
|
||||
template<typename Component>
|
||||
SparseSet<Entity, Component> & assure() {
|
||||
const SparseSet<Entity, Component> & pool() const noexcept {
|
||||
const auto ctype = component_family::type<Component>();
|
||||
assert(ctype < pools.size() && std::get<0>(pools[ctype]));
|
||||
return static_cast<SparseSet<Entity, Component> &>(*std::get<0>(pools[ctype]));
|
||||
}
|
||||
|
||||
template<typename Component>
|
||||
SparseSet<Entity, Component> & pool() noexcept {
|
||||
return const_cast<SparseSet<Entity, Component> &>(const_cast<const Registry *>(this)->pool<Component>());
|
||||
}
|
||||
|
||||
template<typename Component>
|
||||
void assure() {
|
||||
const auto ctype = component_family::type<Component>();
|
||||
|
||||
if(!(ctype < pools.size())) {
|
||||
@@ -88,8 +95,15 @@ class Registry {
|
||||
if(!cpool) {
|
||||
cpool = std::make_unique<SparseSet<Entity, Component>>();
|
||||
}
|
||||
}
|
||||
|
||||
return static_cast<SparseSet<Entity, Component> &>(*cpool);
|
||||
template<typename Tag>
|
||||
void assure(tag_type_t) {
|
||||
const auto ttype = tag_family::type<Tag>();
|
||||
|
||||
if(!(ttype < tags.size())) {
|
||||
tags.resize(ttype + 1);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -104,7 +118,7 @@ public:
|
||||
/*! @brief Unsigned integer type. */
|
||||
using component_type = typename component_family::family_type;
|
||||
/*! @brief Type of sink for the given component. */
|
||||
using sink_type = typename SigH<void(Registry &, Entity)>::sink_type;
|
||||
using sink_type = typename signal_type::sink_type;
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
Registry() = default;
|
||||
@@ -183,7 +197,8 @@ public:
|
||||
*/
|
||||
template<typename Component>
|
||||
void reserve(size_type cap) {
|
||||
assure<Component>().reserve(cap);
|
||||
assure<Component>();
|
||||
pool<Component>().reserve(cap);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -345,6 +360,25 @@ public:
|
||||
*/
|
||||
void destroy(entity_type entity) {
|
||||
assert(valid(entity));
|
||||
|
||||
std::for_each(pools.begin(), pools.end(), [entity, this](auto &&tup) {
|
||||
auto &cpool = std::get<0>(tup);
|
||||
|
||||
if(cpool && cpool->has(entity)) {
|
||||
std::get<2>(tup).publish(*this, entity);
|
||||
cpool->destroy(entity);
|
||||
}
|
||||
});
|
||||
|
||||
std::for_each(tags.begin(), tags.end(), [entity, this](auto &&tup) {
|
||||
auto &tag = std::get<0>(tup);
|
||||
|
||||
if(tag && tag->entity == entity) {
|
||||
std::get<2>(tup).publish(*this, entity);
|
||||
tag.reset();
|
||||
}
|
||||
});
|
||||
|
||||
const auto entt = entity & traits_type::entity_mask;
|
||||
const auto version = (((entity >> traits_type::entity_shift) + 1) & traits_type::version_mask) << traits_type::entity_shift;
|
||||
const auto node = (available ? next : ((entt + 1) & traits_type::entity_mask)) | version;
|
||||
@@ -352,16 +386,6 @@ public:
|
||||
entities[entt] = node;
|
||||
next = entt;
|
||||
++available;
|
||||
|
||||
// changing the number of pools during a destroy is supported now
|
||||
for(std::size_t pos = pools.size(); pos; --pos) {
|
||||
auto &cpool = std::get<0>(pools[pos-1]);
|
||||
|
||||
if(cpool && cpool->has(entity)) {
|
||||
cpool->destroy(entity);
|
||||
std::get<2>(pools[pos-1]).publish(*this, entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -389,15 +413,11 @@ public:
|
||||
Tag & assign(tag_type_t, entity_type entity, Args &&... args) {
|
||||
assert(valid(entity));
|
||||
assert(!has<Tag>());
|
||||
const auto ttype = tag_family::type<Tag>();
|
||||
|
||||
if(!(ttype < tags.size())) {
|
||||
tags.resize(ttype + 1);
|
||||
}
|
||||
|
||||
tags[ttype].reset(new Attaching<Tag>{entity, Tag{std::forward<Args>(args)...}});
|
||||
|
||||
return static_cast<Attaching<Tag> *>(tags[ttype].get())->tag;
|
||||
assure<Tag>(tag_type_t{});
|
||||
auto &tup = tags[tag_family::type<Tag>()];
|
||||
std::get<0>(tup).reset(new Attaching<Tag>{entity, Tag{std::forward<Args>(args)...}});
|
||||
std::get<1>(tup).publish(*this, entity);
|
||||
return get<Tag>();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -423,10 +443,10 @@ public:
|
||||
template<typename Component, typename... Args>
|
||||
Component & assign(entity_type entity, Args &&... args) {
|
||||
assert(valid(entity));
|
||||
auto &pool = assure<Component>();
|
||||
auto &component = pool.construct(entity, std::forward<Args>(args)...);
|
||||
assure<Component>();
|
||||
pool<Component>().construct(entity, std::forward<Args>(args)...);
|
||||
std::get<1>(pools[component_family::type<Component>()]).publish(*this, entity);
|
||||
return component;
|
||||
return pool<Component>().get(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -436,7 +456,10 @@ public:
|
||||
template<typename Tag>
|
||||
void remove() {
|
||||
if(has<Tag>()) {
|
||||
tags[tag_family::type<Tag>()].reset();
|
||||
auto &tup = tags[tag_family::type<Tag>()];
|
||||
auto &tag = std::get<0>(tup);
|
||||
std::get<2>(tup).publish(*this, tag->entity);
|
||||
tag.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -456,8 +479,10 @@ public:
|
||||
template<typename Component>
|
||||
void remove(entity_type entity) {
|
||||
assert(valid(entity));
|
||||
assure<Component>().destroy(entity);
|
||||
std::get<2>(pools[component_family::type<Component>()]).publish(*this, entity);
|
||||
const auto ctype = component_family::type<Component>();
|
||||
assert(ctype < pools.size() && std::get<0>(pools[ctype]));
|
||||
std::get<2>(pools[ctype]).publish(*this, entity);
|
||||
pool<Component>().destroy(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -468,11 +493,15 @@ public:
|
||||
template<typename Tag>
|
||||
bool has() const noexcept {
|
||||
const auto ttype = tag_family::type<Tag>();
|
||||
return (ttype < tags.size() &&
|
||||
// it's a valid tag
|
||||
tags[ttype] &&
|
||||
// the associated entity hasn't been destroyed in the meantime
|
||||
tags[ttype]->entity == (entities[tags[ttype]->entity & traits_type::entity_mask]));
|
||||
bool found = false;
|
||||
|
||||
if(ttype < tags.size()) {
|
||||
auto &tag = std::get<0>(tags[ttype]);
|
||||
// it's a valid tag and the associated entity hasn't been destroyed in the meantime
|
||||
found = tag && (tag->entity == (entities[tag->entity & traits_type::entity_mask]));
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -513,7 +542,7 @@ public:
|
||||
template<typename Tag>
|
||||
const Tag & get() const noexcept {
|
||||
assert(has<Tag>());
|
||||
return static_cast<Attaching<Tag> *>(tags[tag_family::type<Tag>()].get())->tag;
|
||||
return static_cast<Attaching<Tag> *>(std::get<0>(tags[tag_family::type<Tag>()]).get())->tag;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -634,7 +663,7 @@ public:
|
||||
*/
|
||||
template<typename Tag, typename... Args>
|
||||
Tag & replace(tag_type_t, Args &&... args) {
|
||||
return get<Tag>() = Tag{std::forward<Args>(args)...};
|
||||
return (get<Tag>() = Tag{std::forward<Args>(args)...});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -682,9 +711,9 @@ public:
|
||||
entity_type move(entity_type entity) {
|
||||
assert(valid(entity));
|
||||
assert(has<Tag>());
|
||||
const auto ttype = tag_family::type<Tag>();
|
||||
const auto owner = tags[ttype]->entity;
|
||||
tags[ttype]->entity = entity;
|
||||
auto &tag = std::get<0>(tags[tag_family::type<Tag>()]);
|
||||
const auto owner = tag->entity;
|
||||
tag->entity = entity;
|
||||
return owner;
|
||||
}
|
||||
|
||||
@@ -703,7 +732,7 @@ public:
|
||||
template<typename Tag>
|
||||
entity_type attachee() const noexcept {
|
||||
assert(has<Tag>());
|
||||
return tags[tag_family::type<Tag>()]->entity;
|
||||
return std::get<0>(tags[tag_family::type<Tag>()])->entity;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -737,6 +766,35 @@ public:
|
||||
: assign<Component>(entity, std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a sink object for the given tag.
|
||||
*
|
||||
* A sink is an opaque object used to connect listeners to tags.<br/>
|
||||
* The sink returned by this function can be used to receive notifications
|
||||
* whenever a new instance of the given tag is created and assigned to an
|
||||
* entity.
|
||||
*
|
||||
* The function type for a listener is:
|
||||
* @code{.cpp}
|
||||
* void(Registry<Entity> &, Entity);
|
||||
* @endcode
|
||||
*
|
||||
* Listeners are invoked **after** the tag has been assigned to the entity.
|
||||
* The order of invocation of the listeners isn't guaranteed.<br/>
|
||||
* Note also that the greater the number of listeners, the greater the
|
||||
* performance hit when a new tag is created.
|
||||
*
|
||||
* @sa SigH::Sink
|
||||
*
|
||||
* @tparam Tag Type of tag of which to get the sink.
|
||||
* @return A temporary sink object.
|
||||
*/
|
||||
template<typename Tag>
|
||||
sink_type construction(tag_type_t) noexcept {
|
||||
assure<Tag>(tag_type_t{});
|
||||
return std::get<1>(tags[tag_family::type<Tag>()]).sink();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a sink object for the given component.
|
||||
*
|
||||
@@ -751,7 +809,9 @@ public:
|
||||
* @endcode
|
||||
*
|
||||
* Listeners are invoked **after** the component has been assigned to the
|
||||
* entity. The order of invocation of the listeners isn't guaranteed.
|
||||
* entity. The order of invocation of the listeners isn't guaranteed.<br/>
|
||||
* Note also that the greater the number of listeners, the greater the
|
||||
* performance hit when a new component is created.
|
||||
*
|
||||
* @sa SigH::Sink
|
||||
*
|
||||
@@ -760,7 +820,37 @@ public:
|
||||
*/
|
||||
template<typename Component>
|
||||
sink_type construction() noexcept {
|
||||
return (assure<Component>(), std::get<1>(pools[component_family::type<Component>()]).sink());
|
||||
assure<Component>();
|
||||
return std::get<1>(pools[component_family::type<Component>()]).sink();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a sink object for the given tag.
|
||||
*
|
||||
* A sink is an opaque object used to connect listeners to tag.<br/>
|
||||
* The sink returned by this function can be used to receive notifications
|
||||
* whenever an instance of the given tag is removed from an entity and thus
|
||||
* destroyed.
|
||||
*
|
||||
* The function type for a listener is:
|
||||
* @code{.cpp}
|
||||
* void(Registry<Entity> &, Entity);
|
||||
* @endcode
|
||||
*
|
||||
* Listeners are invoked **before** the tag has been removed from the
|
||||
* entity. The order of invocation of the listeners isn't guaranteed.<br/>
|
||||
* Note also that the greater the number of listeners, the greater the
|
||||
* performance hit when a tag is destroyed.
|
||||
*
|
||||
* @sa SigH::Sink
|
||||
*
|
||||
* @tparam Tag Type of tag of which to get the sink.
|
||||
* @return A temporary sink object.
|
||||
*/
|
||||
template<typename Tag>
|
||||
sink_type destruction(tag_type_t) noexcept {
|
||||
assure<Tag>(tag_type_t{});
|
||||
return std::get<2>(tags[tag_family::type<Tag>()]).sink();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -777,7 +867,9 @@ public:
|
||||
* @endcode
|
||||
*
|
||||
* Listeners are invoked **before** the component has been removed from the
|
||||
* entity. The order of invocation of the listeners isn't guaranteed.
|
||||
* entity. The order of invocation of the listeners isn't guaranteed.<br/>
|
||||
* Note also that the greater the number of listeners, the greater the
|
||||
* performance hit when a component is destroyed.
|
||||
*
|
||||
* @sa SigH::Sink
|
||||
*
|
||||
@@ -786,7 +878,8 @@ public:
|
||||
*/
|
||||
template<typename Component>
|
||||
sink_type destruction() noexcept {
|
||||
return (assure<Component>(), std::get<2>(pools[component_family::type<Component>()]).sink());
|
||||
assure<Component>();
|
||||
return std::get<2>(pools[component_family::type<Component>()]).sink();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -814,7 +907,8 @@ public:
|
||||
*/
|
||||
template<typename Component, typename Compare>
|
||||
void sort(Compare compare) {
|
||||
assure<Component>().sort(std::move(compare));
|
||||
assure<Component>();
|
||||
pool<Component>().sort(std::move(compare));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -849,7 +943,9 @@ public:
|
||||
*/
|
||||
template<typename To, typename From>
|
||||
void sort() {
|
||||
assure<To>().respect(assure<From>());
|
||||
assure<To>();
|
||||
assure<From>();
|
||||
pool<To>().respect(pool<From>());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -869,11 +965,13 @@ public:
|
||||
template<typename Component>
|
||||
void reset(entity_type entity) {
|
||||
assert(valid(entity));
|
||||
auto &cpool = assure<Component>();
|
||||
assure<Component>();
|
||||
const auto ctype = component_family::type<Component>();
|
||||
auto &cpool = *std::get<0>(pools[ctype]);
|
||||
|
||||
if(cpool.has(entity)) {
|
||||
std::get<2>(pools[ctype]).publish(*this, entity);
|
||||
cpool.destroy(entity);
|
||||
std::get<2>(pools[component_family::type<Component>()]).publish(*this, entity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -887,15 +985,15 @@ public:
|
||||
*/
|
||||
template<typename Component>
|
||||
void reset() {
|
||||
auto &cpool = assure<Component>();
|
||||
auto &sig = std::get<2>(pools[component_family::type<Component>()]);
|
||||
assure<Component>();
|
||||
const auto ctype = component_family::type<Component>();
|
||||
auto &cpool = *std::get<0>(pools[ctype]);
|
||||
auto &sig = std::get<2>(pools[ctype]);
|
||||
|
||||
each([&cpool, &sig, this](auto entity) {
|
||||
if(cpool.has(entity)) {
|
||||
cpool.destroy(entity);
|
||||
sig.publish(*this, entity);
|
||||
}
|
||||
});
|
||||
for(const auto entity: cpool) {
|
||||
sig.publish(*this, entity);
|
||||
cpool.destroy(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -970,7 +1068,7 @@ public:
|
||||
}
|
||||
|
||||
for(std::size_t i = 0; i < tags.size() && orphan; ++i) {
|
||||
const auto &tag = tags[i];
|
||||
const auto &tag = std::get<0>(tags[i]);
|
||||
orphan = !(tag && (tag->entity == entity));
|
||||
}
|
||||
|
||||
@@ -1037,7 +1135,7 @@ public:
|
||||
*/
|
||||
template<typename... Component>
|
||||
View<Entity, Component...> view() {
|
||||
return View<Entity, Component...>{assure<Component>()...};
|
||||
return View<Entity, Component...>{(assure<Component>(), pool<Component>())...};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1090,7 +1188,7 @@ public:
|
||||
*
|
||||
* Persistent views occupy memory, no matter if they are in use or not.<br/>
|
||||
* This function can be used to discard all the internal data structures
|
||||
* dedicated to a specific persisten view, with the goal of reducing the
|
||||
* dedicated to a specific persistent view, with the goal of reducing the
|
||||
* memory pressure.
|
||||
*
|
||||
* @warning
|
||||
@@ -1173,7 +1271,7 @@ public:
|
||||
PersistentView<Entity, Component...> persistent() {
|
||||
prepare<Component...>();
|
||||
const auto htype = handler_family::type<Component...>();
|
||||
return PersistentView<Entity, Component...>{*handlers[htype], assure<Component>()...};
|
||||
return PersistentView<Entity, Component...>{*handlers[htype], (assure<Component>(), pool<Component>())...};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1200,7 +1298,8 @@ public:
|
||||
*/
|
||||
template<typename Component>
|
||||
RawView<Entity, Component> raw() {
|
||||
return RawView<Entity, Component>{assure<Component>()};
|
||||
assure<Component>();
|
||||
return RawView<Entity, Component>{pool<Component>()};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1277,8 +1376,8 @@ public:
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<SparseSet<Entity>>> handlers;
|
||||
std::vector<std::tuple<std::unique_ptr<SparseSet<Entity>>, SigH<void(Registry &, Entity)>, SigH<void(Registry &, Entity)>>> pools;
|
||||
std::vector<std::unique_ptr<Attachee>> tags;
|
||||
std::vector<std::tuple<std::unique_ptr<SparseSet<Entity>>, signal_type, signal_type>> pools;
|
||||
std::vector<std::tuple<std::unique_ptr<Attachee>, signal_type, signal_type>> tags;
|
||||
std::vector<entity_type> entities;
|
||||
size_type available{};
|
||||
entity_type next{};
|
||||
|
||||
@@ -6,13 +6,37 @@
|
||||
#include <entt/entity/entt_traits.hpp>
|
||||
#include <entt/entity/registry.hpp>
|
||||
|
||||
struct ComponentListener {
|
||||
void incr(entt::DefaultRegistry &, entt::DefaultRegistry::entity_type entity) {
|
||||
struct Listener {
|
||||
template<typename Component>
|
||||
void incrComponent(entt::DefaultRegistry ®istry, entt::DefaultRegistry::entity_type entity) {
|
||||
ASSERT_TRUE(registry.valid(entity));
|
||||
ASSERT_TRUE(registry.has<Component>(entity));
|
||||
last = entity;
|
||||
++counter;
|
||||
}
|
||||
|
||||
void decr(entt::DefaultRegistry &, entt::DefaultRegistry::entity_type entity) {
|
||||
template<typename Tag>
|
||||
void incrTag(entt::DefaultRegistry ®istry, entt::DefaultRegistry::entity_type entity) {
|
||||
ASSERT_TRUE(registry.valid(entity));
|
||||
ASSERT_TRUE(registry.has<Tag>());
|
||||
ASSERT_EQ(registry.attachee<Tag>(), entity);
|
||||
last = entity;
|
||||
++counter;
|
||||
}
|
||||
|
||||
template<typename Component>
|
||||
void decrComponent(entt::DefaultRegistry ®istry, entt::DefaultRegistry::entity_type entity) {
|
||||
ASSERT_TRUE(registry.valid(entity));
|
||||
ASSERT_TRUE(registry.has<Component>(entity));
|
||||
last = entity;
|
||||
--counter;
|
||||
}
|
||||
|
||||
template<typename Tag>
|
||||
void decrTag(entt::DefaultRegistry ®istry, entt::DefaultRegistry::entity_type entity) {
|
||||
ASSERT_TRUE(registry.valid(entity));
|
||||
ASSERT_TRUE(registry.has<Tag>());
|
||||
ASSERT_EQ(registry.attachee<Tag>(), entity);
|
||||
last = entity;
|
||||
--counter;
|
||||
}
|
||||
@@ -568,12 +592,12 @@ TEST(DefaultRegistry, MergeTwoRegistries) {
|
||||
ne(dst.view<char, float, int>().begin(), dst.view<char, float, int>().end());
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, Signals) {
|
||||
TEST(DefaultRegistry, ComponentSignals) {
|
||||
entt::DefaultRegistry registry;
|
||||
ComponentListener listener;
|
||||
Listener listener;
|
||||
|
||||
registry.construction<int>().connect<ComponentListener, &ComponentListener::incr>(&listener);
|
||||
registry.destruction<int>().connect<ComponentListener, &ComponentListener::decr>(&listener);
|
||||
registry.construction<int>().connect<Listener, &Listener::incrComponent<int>>(&listener);
|
||||
registry.destruction<int>().connect<Listener, &Listener::decrComponent<int>>(&listener);
|
||||
|
||||
auto e0 = registry.create();
|
||||
auto e1 = registry.create();
|
||||
@@ -589,16 +613,73 @@ TEST(DefaultRegistry, Signals) {
|
||||
ASSERT_EQ(listener.counter, 1);
|
||||
ASSERT_EQ(listener.last, e0);
|
||||
|
||||
registry.destruction<int>().disconnect<ComponentListener, &ComponentListener::decr>(&listener);
|
||||
registry.destruction<int>().disconnect<Listener, &Listener::decrComponent<int>>(&listener);
|
||||
registry.remove<int>(e1);
|
||||
|
||||
ASSERT_EQ(listener.counter, 1);
|
||||
ASSERT_EQ(listener.last, e0);
|
||||
|
||||
registry.construction<int>().disconnect<ComponentListener, &ComponentListener::incr>(&listener);
|
||||
registry.assign<int>(e0);
|
||||
registry.construction<int>().disconnect<Listener, &Listener::incrComponent<int>>(&listener);
|
||||
registry.assign<int>(e1);
|
||||
|
||||
ASSERT_EQ(listener.counter, 1);
|
||||
ASSERT_EQ(listener.last, e0);
|
||||
|
||||
registry.construction<int>().connect<Listener, &Listener::incrComponent<int>>(&listener);
|
||||
registry.destruction<int>().connect<Listener, &Listener::decrComponent<int>>(&listener);
|
||||
registry.assign<int>(e0);
|
||||
registry.reset<int>(e1);
|
||||
|
||||
ASSERT_EQ(listener.counter, 1);
|
||||
ASSERT_EQ(listener.last, e1);
|
||||
|
||||
registry.reset<int>();
|
||||
|
||||
ASSERT_EQ(listener.counter, 0);
|
||||
ASSERT_EQ(listener.last, e0);
|
||||
|
||||
registry.assign<int>(e0);
|
||||
registry.assign<int>(e1);
|
||||
registry.destroy(e1);
|
||||
|
||||
ASSERT_EQ(listener.counter, 1);
|
||||
ASSERT_EQ(listener.last, e1);
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, TagSignals) {
|
||||
entt::DefaultRegistry registry;
|
||||
Listener listener;
|
||||
|
||||
registry.construction<int>(entt::tag_type_t{}).connect<Listener, &Listener::incrTag<int>>(&listener);
|
||||
registry.destruction<int>(entt::tag_type_t{}).connect<Listener, &Listener::decrTag<int>>(&listener);
|
||||
|
||||
auto e0 = registry.create();
|
||||
registry.assign<int>(entt::tag_type_t{}, e0);
|
||||
|
||||
ASSERT_EQ(listener.counter, 1);
|
||||
ASSERT_EQ(listener.last, e0);
|
||||
|
||||
auto e1 = registry.create();
|
||||
registry.move<int>(e1);
|
||||
registry.remove<int>();
|
||||
|
||||
ASSERT_EQ(listener.counter, 0);
|
||||
ASSERT_EQ(listener.last, e1);
|
||||
|
||||
registry.construction<int>(entt::tag_type_t{}).disconnect<Listener, &Listener::incrTag<int>>(&listener);
|
||||
registry.destruction<int>(entt::tag_type_t{}).disconnect<Listener, &Listener::decrTag<int>>(&listener);
|
||||
registry.assign<int>(entt::tag_type_t{}, e0);
|
||||
registry.remove<int>();
|
||||
|
||||
ASSERT_EQ(listener.counter, 0);
|
||||
ASSERT_EQ(listener.last, e1);
|
||||
|
||||
registry.construction<int>(entt::tag_type_t{}).connect<Listener, &Listener::incrTag<int>>(&listener);
|
||||
registry.destruction<int>(entt::tag_type_t{}).connect<Listener, &Listener::decrTag<int>>(&listener);
|
||||
|
||||
registry.assign<int>(entt::tag_type_t{}, e0);
|
||||
registry.destroy(e0);
|
||||
|
||||
ASSERT_EQ(listener.counter, 0);
|
||||
ASSERT_EQ(listener.last, e0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user