Ver código fonte

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

Michele Caini 6 anos atrás
pai
commit
43766406ae

+ 17 - 22
docs/md/lib.md

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

+ 1 - 12
src/entt/entity/registry.hpp

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

+ 0 - 11
src/entt/signal/dispatcher.hpp

@@ -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.
      *

+ 0 - 11
src/entt/signal/emitter.hpp

@@ -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.
      *

+ 16 - 3
test/lib/dispatcher_plugin/main.cpp

@@ -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);
 

+ 2 - 5
test/lib/dispatcher_plugin/plugin.cpp

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

+ 16 - 0
test/lib/dispatcher_plugin/proxy.h

@@ -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

+ 6 - 0
test/lib/dispatcher_plugin/types.h

@@ -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

+ 16 - 3
test/lib/dispatcher_plugin_std/main.cpp

@@ -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);
 

+ 2 - 5
test/lib/dispatcher_plugin_std/plugin.cpp

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

+ 16 - 0
test/lib/dispatcher_plugin_std/proxy.h

@@ -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

+ 5 - 22
test/lib/dispatcher_plugin_std/types.h

@@ -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

+ 15 - 1
test/lib/emitter_plugin/main.cpp

@@ -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);
 

+ 3 - 6
test/lib/emitter_plugin/plugin.cpp

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

+ 15 - 0
test/lib/emitter_plugin/proxy.h

@@ -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

+ 6 - 0
test/lib/emitter_plugin/types.h

@@ -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

+ 15 - 1
test/lib/emitter_plugin_std/main.cpp

@@ -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);
 

+ 3 - 6
test/lib/emitter_plugin_std/plugin.cpp

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

+ 15 - 0
test/lib/emitter_plugin_std/proxy.h

@@ -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

+ 5 - 21
test/lib/emitter_plugin_std/types.h

@@ -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

+ 17 - 1
test/lib/registry_plugin/main.cpp

@@ -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);
 

+ 5 - 12
test/lib/registry_plugin/plugin.cpp

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

+ 16 - 0
test/lib/registry_plugin/proxy.h

@@ -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

+ 6 - 0
test/lib/registry_plugin/types.h

@@ -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

+ 17 - 1
test/lib/registry_plugin_std/main.cpp

@@ -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);
 

+ 5 - 12
test/lib/registry_plugin_std/plugin.cpp

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

+ 16 - 0
test/lib/registry_plugin_std/proxy.h

@@ -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

+ 5 - 22
test/lib/registry_plugin_std/types.h

@@ -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