registry/dispatcher/emitter: removed ::discard, updated test and doc

This commit is contained in:
Michele Caini
2019-12-29 00:11:04 +01:00
parent 37dd1ae363
commit 43766406ae
28 changed files with 261 additions and 177 deletions

View File

@@ -32,10 +32,12 @@ erased on the other side of a boundary.<br/>
The `type_info` class template is how identifiers are generated and thus made
available to the rest of the library.
The only case in which this may arouse some interest is in case of conflicts
between identifiers (definitely uncommon though) or where the default solution
proposed by `EnTT` is not suitable for the user's purposes.<br/>
Please refer to the dedicated section for more details.
In general, this class doesn't arouse much interest. The only exception is in
case of conflicts between identifiers (definitely uncommon though) or where the
default solution proposed by `EnTT` isn't suitable for the user's purposes.<br/>
The section dedicated to this core class contains all the details to get around
the problem in a concise and elegant way. Please refer to the specific
documentation.
# Meta context
@@ -80,29 +82,22 @@ available.
There is another subtle problem due to memory management that can lead to
headaches.<br/>
This can occur where there are pools of objects (such as components or events)
dynamically created on demand.
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 share it with a plugin. If the latter starts working with a component that
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.<br/>
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.
Fortunately, all classes that could potentially suffer from this problem offer
also a `discard` member function to get rid of these pools:
```cpp
registry.discard<local_type>();
```
This is all there is to do to get around this. Again, `discard` is only to be
invoked if it's certain that the container and pools are instantiated on
different sides of the boundary.
If in doubts or to avoid risks, simply invoke the `prepare` member function or
any of the other functions that refer to the desired type to force the
generation of the pools that are used on both sides of the boundary.<br/>
This is something to be done usually in the main context when needed.
To overcome the risk, it's 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.<br/>
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.

View File

@@ -155,7 +155,7 @@ class basic_registry {
template<typename Component>
void maybe_valid_if(const Entity entt, basic_registry &owner) {
static_assert(std::is_same_v<Component, std::decay_t<Component>>);
const auto cpools = std::forward_as_tuple(owner.assure<Owned>()...);
[[maybe_unused]] const auto cpools = std::forward_as_tuple(owner.assure<Owned>()...);
const auto is_valid = ((std::is_same_v<Component, Owned> || std::get<pool_type<Owned> &>(cpools).has(entt)) && ...)
&& ((std::is_same_v<Component, Get> || owner.assure<Get>().has(entt)) && ...)
@@ -302,17 +302,6 @@ public:
assure<Component>(std::forward<Args>(args)...);
}
/**
* @brief Discards the pools for the given components.
* @tparam Component Types of components for which to discard the pools.
*/
template<typename... Component>
void discard() {
pools.erase(std::remove_if(pools.begin(), pools.end(), [](auto &&pdata) {
return ((pdata.type_id == type_info<Component>::id()) || ...);
}), pools.end());
}
/**
* @brief Returns the number of existing components of the given type.
* @tparam Component Type of component of which to return the size.

View File

@@ -98,17 +98,6 @@ class dispatcher {
}
public:
/**
* @brief Discards the pools for the given events.
* @tparam Event Types of events for which to discard the pools.
*/
template<typename... Event>
void discard() {
pools.erase(std::remove_if(pools.begin(), pools.end(), [](auto &&cpool) {
return ((cpool->type_id() == type_info<Event>::id()) || ...);
}), pools.end());
}
/**
* @brief Returns a sink object for the given event.
*

View File

@@ -186,17 +186,6 @@ public:
/*! @brief Default move assignment operator. @return This emitter. */
emitter & operator=(emitter &&) = default;
/**
* @brief Discards the pools for the given events.
* @tparam Event Types of events for which to discard the pools.
*/
template<typename... Event>
void discard() {
pools.erase(std::remove_if(pools.begin(), pools.end(), [](auto &&cpool) {
return ((cpool->type_id() == type_info<Event>::id()) || ...);
}), pools.end());
}
/**
* @brief Emits the given event.
*

View File

@@ -2,10 +2,22 @@
#include <cr.h>
#include <gtest/gtest.h>
#include <entt/core/utility.hpp>
#include <entt/signal/dispatcher.hpp>
#include "proxy.h"
#include "types.h"
proxy::proxy(entt::dispatcher &ref)
: dispatcher{&ref}
{}
void proxy::trigger(message msg) {
dispatcher->trigger(msg);
}
void proxy::trigger(event ev) {
dispatcher->trigger(ev);
}
struct listener {
void on(message msg) { value = msg.payload; }
int value{};
@@ -13,14 +25,15 @@ struct listener {
TEST(Lib, Dispatcher) {
entt::dispatcher dispatcher;
proxy handler{dispatcher};
listener listener;
ASSERT_EQ(listener.value, 0);
dispatcher.sink<message>().connect<entt::overload<void(message)>(&listener::on)>(listener);
dispatcher.sink<message>().connect<&listener::on>(listener);
cr_plugin ctx;
ctx.userdata = &dispatcher;
ctx.userdata = &handler;
cr_plugin_load(ctx, PLUGIN);
cr_plugin_update(ctx);

View File

@@ -1,16 +1,13 @@
#include <cr.h>
#include <entt/signal/dispatcher.hpp>
#include "types.h"
CR_EXPORT int cr_main(cr_plugin *ctx, cr_op operation) {
switch (operation) {
case CR_STEP:
static_cast<entt::dispatcher *>(ctx->userdata)->trigger<event>();
static_cast<entt::dispatcher *>(ctx->userdata)->trigger<message>(42);
static_cast<dispatcher_proxy *>(ctx->userdata)->trigger(event{});
static_cast<dispatcher_proxy *>(ctx->userdata)->trigger(message{42});
break;
case CR_CLOSE:
static_cast<entt::dispatcher *>(ctx->userdata)->discard<event>();
break;
case CR_LOAD:
case CR_UNLOAD:
// nothing to do here, this is only a test.

View File

@@ -0,0 +1,16 @@
#ifndef ENTT_LIB_DISPATCHER_PLUGIN_PROXY_H
#define ENTT_LIB_DISPATCHER_PLUGIN_PROXY_H
#include <entt/signal/fwd.hpp>
#include "types.h"
struct proxy: dispatcher_proxy {
proxy(entt::dispatcher &);
void trigger(message) override;
void trigger(event) override;
private:
entt::dispatcher *dispatcher;
};
#endif

View File

@@ -7,4 +7,10 @@ struct message {
struct event {};
struct dispatcher_proxy {
virtual ~dispatcher_proxy() = default;
virtual void trigger(message) = 0;
virtual void trigger(event) = 0;
};
#endif

View File

@@ -2,10 +2,22 @@
#include <cr.h>
#include <gtest/gtest.h>
#include <entt/core/utility.hpp>
#include <entt/signal/dispatcher.hpp>
#include "proxy.h"
#include "types.h"
proxy::proxy(entt::dispatcher &ref)
: dispatcher{&ref}
{}
void proxy::trigger(message msg) {
dispatcher->trigger(msg);
}
void proxy::trigger(event ev) {
dispatcher->trigger(ev);
}
struct listener {
void on(message msg) { value = msg.payload; }
int value{};
@@ -13,14 +25,15 @@ struct listener {
TEST(Lib, Dispatcher) {
entt::dispatcher dispatcher;
proxy handler{dispatcher};
listener listener;
ASSERT_EQ(listener.value, 0);
dispatcher.sink<message>().connect<entt::overload<void(message)>(&listener::on)>(listener);
dispatcher.sink<message>().connect<&listener::on>(listener);
cr_plugin ctx;
ctx.userdata = &dispatcher;
ctx.userdata = &handler;
cr_plugin_load(ctx, PLUGIN);
cr_plugin_update(ctx);

View File

@@ -1,16 +1,13 @@
#include <cr.h>
#include <entt/signal/dispatcher.hpp>
#include "types.h"
CR_EXPORT int cr_main(cr_plugin *ctx, cr_op operation) {
switch (operation) {
case CR_STEP:
static_cast<entt::dispatcher *>(ctx->userdata)->trigger<event>();
static_cast<entt::dispatcher *>(ctx->userdata)->trigger<message>(42);
static_cast<dispatcher_proxy *>(ctx->userdata)->trigger(event{});
static_cast<dispatcher_proxy *>(ctx->userdata)->trigger(message{42});
break;
case CR_CLOSE:
static_cast<entt::dispatcher *>(ctx->userdata)->discard<event>();
break;
case CR_LOAD:
case CR_UNLOAD:
// nothing to do here, this is only a test.

View File

@@ -0,0 +1,16 @@
#ifndef ENTT_LIB_DISPATCHER_PLUGIN_STD_PROXY_H
#define ENTT_LIB_DISPATCHER_PLUGIN_STD_PROXY_H
#include <entt/signal/fwd.hpp>
#include "types.h"
struct proxy: dispatcher_proxy {
proxy(entt::dispatcher &);
void trigger(message) override;
void trigger(event) override;
private:
entt::dispatcher *dispatcher;
};
#endif

View File

@@ -1,33 +1,16 @@
#ifndef ENTT_LIB_DISPATCHER_PLUGIN_STD_TYPES_H
#define ENTT_LIB_DISPATCHER_PLUGIN_STD_TYPES_H
#include <type_traits>
#include <entt/core/hashed_string.hpp>
#include <entt/core/type_info.hpp>
template<typename>
struct event_id;
#define ASSIGN_TYPE_ID(clazz)\
template<>\
struct event_id<clazz>\
: std::integral_constant<ENTT_ID_TYPE, entt::basic_hashed_string<std::remove_cv_t<std::remove_pointer_t<std::decay_t<decltype(#clazz)>>>>{#clazz}>\
{}
template<typename Type>
struct entt::type_info<Type> {
static constexpr ENTT_ID_TYPE id() ENTT_NOEXCEPT {
return event_id<Type>::value;
}
};
struct message {
int payload;
};
struct event {};
ASSIGN_TYPE_ID(message);
ASSIGN_TYPE_ID(event);
struct dispatcher_proxy {
virtual ~dispatcher_proxy() = default;
virtual void trigger(message) = 0;
virtual void trigger(event) = 0;
};
#endif

View File

@@ -3,10 +3,24 @@
#include <cr.h>
#include <gtest/gtest.h>
#include <entt/signal/emitter.hpp>
#include "proxy.h"
#include "types.h"
proxy::proxy(test_emitter &ref)
: emitter{&ref}
{}
void proxy::publish(message msg) {
emitter->publish<message>(msg);
}
void proxy::publish(event ev) {
emitter->publish<event>(ev);
}
TEST(Lib, Emitter) {
test_emitter emitter;
proxy handler{emitter};
int value{};
ASSERT_EQ(value, 0);
@@ -14,7 +28,7 @@ TEST(Lib, Emitter) {
emitter.once<message>([&](message msg, test_emitter &) { value = msg.payload; });
cr_plugin ctx;
ctx.userdata = &emitter;
ctx.userdata = &handler;
cr_plugin_load(ctx, PLUGIN);
cr_plugin_update(ctx);

View File

@@ -1,17 +1,14 @@
#include <cr.h>
#include <entt/signal/emitter.hpp>
#include "types.h"
CR_EXPORT int cr_main(cr_plugin *ctx, cr_op operation) {
switch (operation) {
case CR_STEP:
static_cast<test_emitter *>(ctx->userdata)->publish<event>();
static_cast<test_emitter *>(ctx->userdata)->publish<message>(42);
static_cast<test_emitter *>(ctx->userdata)->publish<message>(3);
static_cast<emitter_proxy *>(ctx->userdata)->publish(event{});
static_cast<emitter_proxy *>(ctx->userdata)->publish(message{42});
static_cast<emitter_proxy *>(ctx->userdata)->publish(message{3});
break;
case CR_CLOSE:
static_cast<test_emitter *>(ctx->userdata)->discard<event>();
break;
case CR_LOAD:
case CR_UNLOAD:
// nothing to do here, this is only a test.

View File

@@ -0,0 +1,15 @@
#ifndef ENTT_LIB_EMITTER_PLUGIN_PROXY_H
#define ENTT_LIB_EMITTER_PLUGIN_PROXY_H
#include "types.h"
struct proxy: emitter_proxy {
proxy(test_emitter &);
void publish(message) override;
void publish(event) override;
private:
test_emitter *emitter;
};
#endif

View File

@@ -13,4 +13,10 @@ struct message {
struct event {};
struct emitter_proxy {
virtual ~emitter_proxy() = default;
virtual void publish(message) = 0;
virtual void publish(event) = 0;
};
#endif

View File

@@ -3,10 +3,24 @@
#include <cr.h>
#include <gtest/gtest.h>
#include <entt/signal/emitter.hpp>
#include "proxy.h"
#include "types.h"
proxy::proxy(test_emitter &ref)
: emitter{&ref}
{}
void proxy::publish(message msg) {
emitter->publish<message>(msg);
}
void proxy::publish(event ev) {
emitter->publish<event>(ev);
}
TEST(Lib, Emitter) {
test_emitter emitter;
proxy handler{emitter};
int value{};
ASSERT_EQ(value, 0);
@@ -14,7 +28,7 @@ TEST(Lib, Emitter) {
emitter.once<message>([&](message msg, test_emitter &) { value = msg.payload; });
cr_plugin ctx;
ctx.userdata = &emitter;
ctx.userdata = &handler;
cr_plugin_load(ctx, PLUGIN);
cr_plugin_update(ctx);

View File

@@ -1,17 +1,14 @@
#include <cr.h>
#include <entt/signal/emitter.hpp>
#include "types.h"
CR_EXPORT int cr_main(cr_plugin *ctx, cr_op operation) {
switch (operation) {
case CR_STEP:
static_cast<test_emitter *>(ctx->userdata)->publish<event>();
static_cast<test_emitter *>(ctx->userdata)->publish<message>(42);
static_cast<test_emitter *>(ctx->userdata)->publish<message>(3);
static_cast<emitter_proxy *>(ctx->userdata)->publish(event{});
static_cast<emitter_proxy *>(ctx->userdata)->publish(message{42});
static_cast<emitter_proxy *>(ctx->userdata)->publish(message{3});
break;
case CR_CLOSE:
static_cast<test_emitter *>(ctx->userdata)->discard<event>();
break;
case CR_LOAD:
case CR_UNLOAD:
// nothing to do here, this is only a test.

View File

@@ -0,0 +1,15 @@
#ifndef ENTT_LIB_EMITTER_PLUGIN_STD_PROXY_H
#define ENTT_LIB_EMITTER_PLUGIN_STD_PROXY_H
#include "types.h"
struct proxy: emitter_proxy {
proxy(test_emitter &);
void publish(message) override;
void publish(event) override;
private:
test_emitter *emitter;
};
#endif

View File

@@ -1,27 +1,8 @@
#ifndef ENTT_LIB_EMITTER_PLUGIN_STD_TYPES_H
#define ENTT_LIB_EMITTER_PLUGIN_STD_TYPES_H
#include <type_traits>
#include <entt/core/hashed_string.hpp>
#include <entt/core/type_info.hpp>
#include <entt/signal/emitter.hpp>
template<typename>
struct event_id;
#define ASSIGN_TYPE_ID(clazz)\
template<>\
struct event_id<clazz>\
: std::integral_constant<ENTT_ID_TYPE, entt::basic_hashed_string<std::remove_cv_t<std::remove_pointer_t<std::decay_t<decltype(#clazz)>>>>{#clazz}>\
{}
template<typename Type>
struct entt::type_info<Type> {
static constexpr ENTT_ID_TYPE id() ENTT_NOEXCEPT {
return event_id<Type>::value;
}
};
struct test_emitter
: entt::emitter<test_emitter>
{};
@@ -32,7 +13,10 @@ struct message {
struct event {};
ASSIGN_TYPE_ID(message);
ASSIGN_TYPE_ID(event);
struct emitter_proxy {
virtual ~emitter_proxy() = default;
virtual void publish(message) = 0;
virtual void publish(event) = 0;
};
#endif

View File

@@ -3,10 +3,26 @@
#include <cr.h>
#include <gtest/gtest.h>
#include <entt/entity/registry.hpp>
#include "proxy.h"
#include "types.h"
proxy::proxy(entt::registry &ref)
: registry{&ref}
{}
void proxy::for_each(void(*cb)(position &, velocity &)) {
registry->view<position, velocity>().each(cb);
}
void proxy::assign(velocity vel) {
for(auto entity: registry->view<position>()) {
registry->assign<velocity>(entity, vel);
}
}
TEST(Lib, Registry) {
entt::registry registry;
proxy handler{registry};
for(auto i = 0; i < 3; ++i) {
const auto entity = registry.create();
@@ -14,7 +30,7 @@ TEST(Lib, Registry) {
}
cr_plugin ctx;
ctx.userdata = &registry;
ctx.userdata = &handler;
cr_plugin_load(ctx, PLUGIN);
cr_plugin_update(ctx);

View File

@@ -1,28 +1,21 @@
#include <cr.h>
#include <entt/entity/registry.hpp>
#include "types.h"
CR_EXPORT int cr_main(cr_plugin *ctx, cr_op operation) {
switch (operation) {
case CR_STEP:
[ctx]() {
auto *registry = static_cast<entt::registry *>(ctx->userdata);
// forces the creation of the pool for the velocity component
registry->prepare<velocity>();
auto *proxy = static_cast<registry_proxy *>(ctx->userdata);
for(auto entity: registry->view<position>()) {
registry->assign<velocity>(entity, 1., 1.);
}
proxy->assign({1., 1.});
registry->view<position, velocity>().each([](auto &pos, auto &vel) {
pos.x += 16 * vel.dx;
pos.y += 16 * vel.dy;
proxy->for_each([](auto &pos, auto &vel) {
pos.x += static_cast<int>(16 * vel.dx);
pos.y += static_cast<int>(16 * vel.dy);
});
}();
break;
case CR_CLOSE:
static_cast<entt::registry *>(ctx->userdata)->discard<velocity>();
break;
case CR_LOAD:
case CR_UNLOAD:
// nothing to do here, this is only a test.

View File

@@ -0,0 +1,16 @@
#ifndef ENTT_LIB_REGISTRY_PLUGIN_PROXY_H
#define ENTT_LIB_REGISTRY_PLUGIN_PROXY_H
#include <entt/entity/fwd.hpp>
#include "types.h"
struct proxy: registry_proxy {
proxy(entt::registry &);
void for_each(void(*)(position &, velocity &)) override;
void assign(velocity) override;
private:
entt::registry *registry;
};
#endif

View File

@@ -11,4 +11,10 @@ struct velocity {
double dy;
};
struct registry_proxy {
virtual ~registry_proxy() = default;
virtual void for_each(void(*)(position &, velocity &)) = 0;
virtual void assign(velocity) = 0;
};
#endif

View File

@@ -3,10 +3,26 @@
#include <cr.h>
#include <gtest/gtest.h>
#include <entt/entity/registry.hpp>
#include "proxy.h"
#include "types.h"
proxy::proxy(entt::registry &ref)
: registry{&ref}
{}
void proxy::for_each(void(*cb)(position &, velocity &)) {
registry->view<position, velocity>().each(cb);
}
void proxy::assign(velocity vel) {
for(auto entity: registry->view<position>()) {
registry->assign<velocity>(entity, vel);
}
}
TEST(Lib, Registry) {
entt::registry registry;
proxy handler{registry};
for(auto i = 0; i < 3; ++i) {
const auto entity = registry.create();
@@ -14,7 +30,7 @@ TEST(Lib, Registry) {
}
cr_plugin ctx;
ctx.userdata = &registry;
ctx.userdata = &handler;
cr_plugin_load(ctx, PLUGIN);
cr_plugin_update(ctx);

View File

@@ -1,28 +1,21 @@
#include <cr.h>
#include <entt/entity/registry.hpp>
#include "types.h"
CR_EXPORT int cr_main(cr_plugin *ctx, cr_op operation) {
switch (operation) {
case CR_STEP:
[ctx]() {
auto *registry = static_cast<entt::registry *>(ctx->userdata);
// forces the creation of the pool for the velocity component
registry->prepare<velocity>();
auto *proxy = static_cast<registry_proxy *>(ctx->userdata);
for(auto entity: registry->view<position>()) {
registry->assign<velocity>(entity, 1., 1.);
}
proxy->assign({1., 1.});
registry->view<position, velocity>().each([](auto &pos, auto &vel) {
pos.x += 16 * vel.dx;
pos.y += 16 * vel.dy;
proxy->for_each([](auto &pos, auto &vel) {
pos.x += static_cast<int>(16 * vel.dx);
pos.y += static_cast<int>(16 * vel.dy);
});
}();
break;
case CR_CLOSE:
static_cast<entt::registry *>(ctx->userdata)->discard<velocity>();
break;
case CR_LOAD:
case CR_UNLOAD:
// nothing to do here, this is only a test.

View File

@@ -0,0 +1,16 @@
#ifndef ENTT_LIB_REGISTRY_PLUGIN_STD_PROXY_H
#define ENTT_LIB_REGISTRY_PLUGIN_STD_PROXY_H
#include <entt/entity/fwd.hpp>
#include "types.h"
struct proxy: registry_proxy {
proxy(entt::registry &);
void for_each(void(*)(position &, velocity &)) override;
void assign(velocity) override;
private:
entt::registry *registry;
};
#endif

View File

@@ -1,26 +1,6 @@
#ifndef ENTT_LIB_REGISTRY_PLUGIN_STD_TYPES_H
#define ENTT_LIB_REGISTRY_PLUGIN_STD_TYPES_H
#include <type_traits>
#include <entt/core/hashed_string.hpp>
#include <entt/core/type_info.hpp>
template<typename>
struct component_id;
#define ASSIGN_TYPE_ID(clazz)\
template<>\
struct component_id<clazz>\
: std::integral_constant<ENTT_ID_TYPE, entt::basic_hashed_string<std::remove_cv_t<std::remove_pointer_t<std::decay_t<decltype(#clazz)>>>>{#clazz}>\
{}
template<typename Type>
struct entt::type_info<Type> {
static constexpr ENTT_ID_TYPE id() ENTT_NOEXCEPT {
return component_id<Type>::value;
}
};
struct position {
int x;
int y;
@@ -31,7 +11,10 @@ struct velocity {
double dy;
};
ASSIGN_TYPE_ID(position);
ASSIGN_TYPE_ID(velocity);
struct registry_proxy {
virtual ~registry_proxy() = default;
virtual void for_each(void(*)(position &, velocity &)) = 0;
virtual void assign(velocity) = 0;
};
#endif