Michele Caini пре 7 година
родитељ
комит
617635a989

+ 1 - 0
CMakeLists.txt

@@ -187,6 +187,7 @@ if(BUILD_TESTING)
     endif()
 
     option(BUILD_BENCHMARK "Build benchmark." OFF)
+    option(BUILD_LIB "Build lib example." OFF)
     option(BUILD_MOD "Build mod example." OFF)
     option(BUILD_SNAPSHOT "Build snapshot example." OFF)
 

+ 11 - 1
TODO

@@ -7,7 +7,6 @@
 * define systems as composable mixins (initializazion, reactive, update, whatever) with flexible auto-detected arguments (registry, views, etc)
 * runner proposal: https://en.wikipedia.org/wiki/Fork%E2%80%93join_model https://slide-rs.github.io/specs/03_dispatcher.html
 * work stealing job system (see #100)
-* can we do more for shared libraries? who knows... see #144
 * meta: sort of meta view based on meta stuff to iterate entities, void * and meta info objects
 * meta: move-to-head optimization when searching by name on parts (data, func, etc)
 * hashed string: add implicit check on construction for uniqueness (optional)
@@ -20,3 +19,14 @@
 * provide create with a pack of default constructible components to assign
 * allow to replace std:: with custom implementations
 * allow to sort groups (::respect can already work with begin/end instead of a whole sparse set)
+<<<<<<< HEAD
+=======
+
+==> can we do more for shared libraries? who knows... see #144
+- use stable component identifiers independent from families and hashed strings
+* to be updated: runtime view (also in doc, no longer missing pools)
+* to be updated: dispatcher
+* to be updated: registry
+* to be updated: emitter
+* to be updated: doc
+>>>>>>> experimental

+ 1 - 1
appveyor.yml

@@ -14,7 +14,7 @@ configuration:
 
 before_build:
   - cd %BUILD_DIR%
-  - cmake .. -DBUILD_TESTING=ON -DCMAKE_CXX_FLAGS=/W1 -G"Visual Studio 15 2017"
+  - cmake .. -DBUILD_TESTING=ON -DBUILD_LIB=ON -DCMAKE_CXX_FLAGS=/W1 -G"Visual Studio 15 2017"
 
 after_build:
   - ctest -C Release -j4

+ 2 - 2
docs/core.md

@@ -103,7 +103,7 @@ Indeed it mostly depends on the flow of execution.
 
 # Hashed strings
 
-A hashed string is a zero overhead resource identifier. Users can use
+A hashed string is a zero overhead unique identifier. Users can use
 human-readable identifiers in the codebase while using their numeric
 counterparts at runtime, thus without affecting performance.<br/>
 The class has an implicit `constexpr` constructor that chews a bunch of
@@ -138,7 +138,7 @@ possible. This is a fact.<br/>
 There is no silver bullet to solve the problem of conflicts when dealing with
 hashing functions. In this case, the best solution seemed to be to give up.
 That's all.<br/>
-After all, human-readable resource identifiers aren't something strictly defined
+After all, human-readable unique identifiers aren't 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.

+ 2 - 2
src/entt/config/config.h

@@ -22,9 +22,9 @@ using maybe_atomic_t = Type;
 #endif // ENTT_USE_ATOMIC
 
 
-#ifndef ENTT_HASH_TYPE
+#ifndef ENTT_ID_TYPE
 #include <cstdint>
-#define ENTT_HASH_TYPE std::uint32_t
+#define ENTT_ID_TYPE std::uint32_t
 #endif
 
 

+ 2 - 3
src/entt/core/family.hpp

@@ -3,7 +3,6 @@
 
 
 #include <type_traits>
-#include <cstddef>
 #include "../config/config.h"
 
 
@@ -19,14 +18,14 @@ namespace entt {
  */
 template<typename...>
 class family {
-    inline static maybe_atomic_t<std::size_t> identifier;
+    inline static maybe_atomic_t<ENTT_ID_TYPE> identifier;
 
     template<typename...>
     inline static const auto inner = identifier++;
 
 public:
     /*! @brief Unsigned integer type. */
-    using family_type = std::size_t;
+    using family_type = ENTT_ID_TYPE;
 
     /*! @brief Statically generated unique identifier for the given type. */
     template<typename... Type>

+ 3 - 3
src/entt/core/hashed_string.hpp

@@ -56,7 +56,7 @@ struct fnv1a_traits<std::uint64_t> {
  * required.
  */
 class hashed_string {
-    using traits_type = internal::fnv1a_traits<ENTT_HASH_TYPE>;
+    using traits_type = internal::fnv1a_traits<ENTT_ID_TYPE>;
 
     struct const_wrapper {
         // non-explicit constructor on purpose
@@ -65,13 +65,13 @@ class hashed_string {
     };
 
     // Fowler–Noll–Vo hash function v. 1a - the good
-    inline static constexpr ENTT_HASH_TYPE helper(ENTT_HASH_TYPE partial, const char *str) ENTT_NOEXCEPT {
+    inline static constexpr ENTT_ID_TYPE helper(ENTT_ID_TYPE partial, const char *str) ENTT_NOEXCEPT {
         return str[0] == 0 ? partial : helper((partial^str[0])*traits_type::prime, str+1);
     }
 
 public:
     /*! @brief Unsigned integer type. */
-    using hash_type = ENTT_HASH_TYPE;
+    using hash_type = ENTT_ID_TYPE;
 
     /**
      * @brief Returns directly the numeric representation of a string.

+ 3 - 4
src/entt/core/ident.hpp

@@ -3,7 +3,6 @@
 
 
 #include <type_traits>
-#include <cstddef>
 #include <utility>
 #include <tuple>
 #include "../config/config.h"
@@ -44,14 +43,14 @@ class identifier {
     using tuple_type = std::tuple<std::decay_t<Types>...>;
 
     template<typename Type, std::size_t... Indexes>
-    static constexpr std::size_t get(std::index_sequence<Indexes...>) ENTT_NOEXCEPT {
+    static constexpr ENTT_ID_TYPE get(std::index_sequence<Indexes...>) ENTT_NOEXCEPT {
         static_assert(std::disjunction_v<std::is_same<Type, Types>...>);
-        return (0 + ... + (std::is_same_v<Type, std::tuple_element_t<Indexes, tuple_type>> ? Indexes : std::size_t{}));
+        return (0 + ... + (std::is_same_v<Type, std::tuple_element_t<Indexes, tuple_type>> ? ENTT_ID_TYPE(Indexes) : ENTT_ID_TYPE{}));
     }
 
 public:
     /*! @brief Unsigned integer type. */
-    using identifier_type = std::size_t;
+    using identifier_type = ENTT_ID_TYPE;
 
     /*! @brief Statically generated unique identifier for the given type. */
     template<typename Type>

+ 68 - 0
src/entt/core/type_traits.hpp

@@ -2,6 +2,10 @@
 #define ENTT_CORE_TYPE_TRAITS_HPP
 
 
+#include <type_traits>
+#include "../core/hashed_string.hpp"
+
+
 namespace entt {
 
 
@@ -34,6 +38,70 @@ constexpr auto type_list_cat(type_list<Type...>, type_list<Other...>, List...) {
 }
 
 
+/*! @brief Traits class used mainly to push things across boundaries. */
+template<typename>
+struct shared_traits;
+
+
+/**
+ * @brief Makes an already existing type a shared type.
+ * @param type Type to make shareable.
+ */
+#define ENTT_SHARED_TYPE(type)\
+    template<>\
+    struct shared_traits<type>\
+        : std::integral_constant<typename hashed_string::hash_type, hashed_string::to_value(#type)>\
+    {}
+
+
+/**
+ * @brief Defines a type as shareable (to use for structs).
+ * @param clazz Name of the type to make shareable.
+ */
+#define ENTT_SHARED_STRUCT(clazz)\
+    struct clazz;\
+    ENTT_SHARED_TYPE(clazz)\
+    struct clazz
+
+
+/**
+ * @brief Defines a type as shareable (to use for classes).
+ * @param clazz Name of the type to make shareable.
+ */
+#define ENTT_SHARED_CLASS(clazz)\
+    class clazz;\
+    ENTT_SHARED_TYPE(clazz)\
+    class clazz
+
+
+/**
+ * @brief Provides the member constant `value` to true if a given type is
+ * shared. In all other cases, `value` is false.
+ */
+template<typename>
+struct is_shared: std::false_type {};
+
+
+/**
+ * @brief Provides the member constant `value` to true if a given type is
+ * shared. In all other cases, `value` is false.
+ * @tparam Type Potentially shared type.
+ */
+template<typename Type>
+struct is_shared<shared_traits<Type>>: std::true_type {};
+
+
+/**
+ * @brief Helper variable template.
+ *
+ * True if a given type is shared, false otherwise.
+ *
+ * @tparam Type Potentially shared type.
+ */
+template<class Type>
+constexpr auto is_shared_v = is_shared<Type>::value;
+
+
 }
 
 

+ 112 - 73
src/entt/entity/registry.hpp

@@ -17,6 +17,7 @@
 #include "../config/config.h"
 #include "../core/algorithm.hpp"
 #include "../core/family.hpp"
+#include "../core/hashed_string.hpp"
 #include "../core/type_traits.hpp"
 #include "../signal/sigh.hpp"
 #include "entity.hpp"
@@ -63,9 +64,11 @@ class registry {
     using signal_type = sigh<void(registry &, const Entity)>;
     using traits_type = entt_traits<Entity>;
 
-    struct signals {
+    struct pool_data {
+        std::unique_ptr<sparse_set<Entity>> pool;
         signal_type construction;
         signal_type destruction;
+        ENTT_ID_TYPE runtime_type;
     };
 
     struct basic_descriptor {
@@ -85,17 +88,17 @@ class registry {
             if(reg.has<Owned..., Get...>(entity) && (0 + ... + reg.has<Exclude>(entity)) == Accepted) {
                 const auto curr = data++;
                 (std::swap(reg.pool<Owned>().get(entity), reg.pool<Owned>().raw()[curr]), ...);
-                (reg.pool<Owned>().swap(reg.pools[reg.type<Owned>()]->get(entity), curr), ...);
+                (reg.pool<Owned>().swap(reg.pool<Owned>().sparse_set<Entity>::get(entity), curr), ...);
             }
         }
 
         void discard_if(registry &reg, const Entity entity) {
-            const auto ctype = type<std::tuple_element_t<0, std::tuple<Owned...>>>();
+            auto &cpool = reg.pool<std::tuple_element_t<0, std::tuple<Owned...>>>();
 
-            if(reg.pools[ctype]->has(entity) && reg.pools[ctype]->get(entity) < data) {
+            if(cpool.has(entity) && cpool.sparse_set<Entity>::get(entity) < data) {
                 const auto curr = --data;
                 (std::swap(reg.pool<Owned>().get(entity), reg.pool<Owned>().raw()[curr]), ...);
-                (reg.pool<Owned>().swap(reg.pools[reg.type<Owned>()]->get(entity), curr), ...);
+                (reg.pool<Owned>().swap(reg.pool<Owned>().sparse_set<Entity>::get(entity), curr), ...);
             }
         }
 
@@ -126,10 +129,23 @@ class registry {
         }
     };
 
+    inline auto handler(typename component_family::family_type ctype) const ENTT_NOEXCEPT {
+        return std::find_if(pools.begin(), pools.end(), [ctype](const auto &pdata) {
+            return pdata.pool && pdata.runtime_type == ctype;
+        });
+    }
+
     template<typename Component>
     inline const auto & pool() const ENTT_NOEXCEPT {
-        assert(type<Component>() < pools.size() && pools[type<Component>()]);
-        return static_cast<const sparse_set<Entity, std::decay_t<Component>> &>(*pools[type<Component>()]);
+        if constexpr(is_shared_v<Component>) {
+            const auto it = handler(type<Component>());
+            assert(it != pools.cend() && it->pool);
+            return static_cast<const sparse_set<Entity, std::decay_t<Component>> &>(it->pool);
+        } else {
+            const auto ctype = type<Component>();
+            assert(ctype < pools.size() && pools[ctype].pool && pools[ctype].runtime_type == ctype);
+            return static_cast<const sparse_set<Entity, std::decay_t<Component>> &>(*pools[ctype].pool);
+        }
     }
 
     template<typename Component>
@@ -139,16 +155,32 @@ class registry {
 
     template<typename Component>
     auto & assure() const {
-        if(!(type<Component>() < pools.size())) {
-            pools.resize(type<Component>()+1);
-            sighs.resize(type<Component>()+1);
+        const auto ctype = type<Component>();
+        pool_data *pdata = nullptr;
+
+        if constexpr(is_shared_v<Component>) {
+            const auto it = handler(type<Component>());
+            pdata = (it == pools.cend() ? &pools.emplace_back() : &(*it));
+        } else {
+            if(!(ctype < pools.size())) {
+                pools.resize(ctype+1);
+            }
+
+            pdata = &pools[ctype];
+
+            if(pdata->pool && pdata->runtime_type != ctype) {
+                pools.emplace_back();
+                std::swap(pools[ctype], pools.back());
+                pdata = &pools[ctype];
+            }
         }
 
-        if(!pools[type<Component>()]) {
-            pools[type<Component>()] = std::make_unique<sparse_set<Entity, std::decay_t<Component>>>();
+        if(!pdata->pool) {
+            pdata->pool = std::make_unique<sparse_set<Entity, std::decay_t<Component>>>();
+            pdata->runtime_type = ctype;
         }
 
-        return pool<Component>();
+        return static_cast<const sparse_set<Entity, std::decay_t<Component>> &>(*pdata->pool);
     }
 
     template<typename Component>
@@ -168,23 +200,6 @@ public:
     /*! @brief Type of sink for the given component. */
     using sink_type = typename signal_type::sink_type;
 
-    /**
-     * @brief Returns the numeric identifier of a type of component at runtime.
-     *
-     * The given component doesn't need to be necessarily in use. However, the
-     * registry could decide to prepare internal data structures for it for
-     * later uses.<br/>
-     * Do not use this functionality to provide numeric identifiers to types at
-     * runtime.
-     *
-     * @tparam Component Type of component to query.
-     * @return Runtime numeric identifier of the given type of component.
-     */
-    template<typename Component>
-    static component_type type() ENTT_NOEXCEPT {
-        return component_family::type<Component>;
-    }
-
     /*! @brief Default constructor. */
     registry() ENTT_NOEXCEPT = default;
 
@@ -198,6 +213,26 @@ public:
     /*! @brief Default move assignment operator. @return This registry. */
     registry & operator=(registry &&) = default;
 
+    /**
+     * @brief Returns the numeric identifier of a component.
+     *
+     * The given component doesn't need to be necessarily in use.<br/>
+     * Do not use this functionality to provide numeric identifiers to types at
+     * runtime, because they aren't guaranteed to be stable between different
+     * runs.
+     *
+     * @tparam Component Type of component to query.
+     * @return Runtime numeric identifier of the given type of component.
+     */
+    template<typename Component>
+    static component_type type() ENTT_NOEXCEPT {
+        if constexpr(is_shared_v<Component>) {
+            return shared_traits<Component>::value;
+        } else {
+            return component_family::type<Component>;
+        }
+    }
+
     /**
      * @brief Returns the number of existing components of the given type.
      * @tparam Component Type of component of which to return the size.
@@ -511,11 +546,11 @@ public:
         assert(valid(entity));
 
         for(auto pos = pools.size(); pos; --pos) {
-            auto &cpool = pools[pos-1];
+            auto &pdata = pools[pos-1];
 
-            if(cpool && cpool->has(entity)) {
-                sighs[pos-1].destruction.publish(*this, entity);
-                cpool->destroy(entity);
+            if(pdata.pool && pdata.pool->has(entity)) {
+                pools[pos-1].destruction.publish(*this, entity);
+                pdata.pool->destroy(entity);
             }
         };
 
@@ -576,7 +611,7 @@ public:
     Component & assign(const entity_type entity, Args &&... args) {
         assert(valid(entity));
         auto &component = assure<Component>().construct(entity, std::forward<Args>(args)...);
-        sighs[type<Component>()].construction.publish(*this, entity);
+        handler(type<Component>())->construction.publish(*this, entity);
         return component;
     }
 
@@ -596,7 +631,7 @@ public:
     template<typename Component>
     void remove(const entity_type entity) {
         assert(valid(entity));
-        sighs[type<Component>()].destruction.publish(*this, entity);
+        handler(type<Component>())->destruction.publish(*this, entity);
         pool<Component>().destroy(entity);
     }
 
@@ -684,7 +719,7 @@ public:
 
         if(!comp) {
             comp = &cpool.construct(entity, std::forward<Component>(component));
-            sighs[type<Component>()].construction.publish(*this, entity);
+            handler(type<Component>())->construction.publish(*this, entity);
         }
 
         return *comp;
@@ -783,7 +818,7 @@ public:
             *comp = std::decay_t<Component>{std::forward<Args>(args)...};
         } else {
             comp = &cpool.construct(entity, std::forward<Args>(args)...);
-            sighs[type<Component>()].construction.publish(*this, entity);
+            handler(type<Component>())->construction.publish(*this, entity);
         }
 
         return *comp;
@@ -815,7 +850,7 @@ public:
     template<typename Component>
     sink_type construction() ENTT_NOEXCEPT {
         assure<Component>();
-        return sighs[type<Component>()].construction.sink();
+        return handler(type<Component>())->construction.sink();
     }
 
     /**
@@ -844,7 +879,7 @@ public:
     template<typename Component>
     sink_type destruction() ENTT_NOEXCEPT {
         assure<Component>();
-        return sighs[type<Component>()].destruction.sink();
+        return handler(type<Component>())->destruction.sink();
     }
 
     /**
@@ -966,7 +1001,7 @@ public:
         auto &cpool = assure<Component>();
 
         if(cpool.has(entity)) {
-            sighs[type<Component>()].destruction.publish(*this, entity);
+            handler(type<Component>())->destruction.publish(*this, entity);
             cpool.destroy(entity);
         }
     }
@@ -982,7 +1017,7 @@ public:
     template<typename Component>
     void reset() {
         auto &cpool = assure<Component>();
-        auto &sigh = sighs[type<Component>()].destruction;
+        auto &sigh = handler(type<Component>())->destruction;
 
         if(sigh.empty()) {
             // no group set, otherwise the signal wouldn't be empty
@@ -1063,9 +1098,9 @@ public:
         assert(valid(entity));
         bool orphan = true;
 
-        for(std::size_t i = 0; i < pools.size() && orphan; ++i) {
-            const auto &cpool = pools[i];
-            orphan = !(cpool && cpool->has(entity));
+        for(std::size_t i = {}; i < pools.size() && orphan; ++i) {
+            const auto &pdata = pools[i];
+            orphan = !(pdata.pool && pdata.pool->has(entity));
         }
 
         return orphan;
@@ -1148,7 +1183,7 @@ public:
      */
     template<typename Component>
     bool owned() const ENTT_NOEXCEPT {
-        return std::any_of(groups.cbegin(), groups.cend(), [](const auto &curr) {
+        return std::any_of(groups.cbegin(), groups.cend(), [this](const auto &curr) {
             return curr && curr->owns(type<Component>());
         });
     }
@@ -1200,27 +1235,35 @@ public:
             assert((!owned<Owned>() && ...));
             groups[gtype] = std::make_unique<descriptor_type>();
             auto *curr = static_cast<descriptor_type *>(groups[gtype].get());
+            decltype(handler({})) it;
 
             if constexpr(sizeof...(Owned) == 0) {
-                ((sighs[type<Get>()].destruction.sink().template connect<&descriptor_type::destroy_if>(curr)), ...);
-                ((sighs[type<Get>()].construction.sink().template connect<&descriptor_type::template construct_if<0>>(curr)), ...);
-                ((sighs[type<Exclude>()].destruction.sink().template connect<&descriptor_type::template construct_if<1>>(curr)), ...);
-                ((sighs[type<Exclude>()].construction.sink().template connect<&descriptor_type::destroy_if>(curr)), ...);
+                ((it = handler(type<Get>()),
+                        it->destruction.sink().template connect<&descriptor_type::destroy_if>(curr),
+                        it->construction.sink().template connect<&descriptor_type::template construct_if<0>>(curr)),
+                        ...);
+
+                ((it = handler(type<Exclude>()),
+                        it->destruction.sink().template connect<&descriptor_type::template construct_if<1>>(curr),
+                        it->construction.sink().template connect<&descriptor_type::destroy_if>(curr)), ...);
 
                 for(const auto entity: view<Get...>()) {
                     curr->template construct_if<0>(*this, entity);
                 }
             } else {
-                (sighs[type<Owned>()].construction.sink().template connect<&descriptor_type::template induce_if<0>>(curr), ...);
-                (sighs[type<Get>()].construction.sink().template connect<&descriptor_type::template induce_if<0>>(curr), ...);
+                ((it = handler(type<Get>()),
+                        it->construction.sink().template connect<&descriptor_type::template induce_if<0>>(curr),
+                        it->destruction.sink().template connect<&descriptor_type::discard_if>(curr)), ...);
 
-                (sighs[type<Owned>()].destruction.sink().template connect<&descriptor_type::discard_if>(curr), ...);
-                (sighs[type<Get>()].destruction.sink().template connect<&descriptor_type::discard_if>(curr), ...);
+                ((it = handler(type<Exclude>()),
+                        it->destruction.sink().template connect<&descriptor_type::template induce_if<1>>(curr),
+                        it->construction.sink().template connect<&descriptor_type::discard_if>(curr)), ...);
 
-                (sighs[type<Exclude>()].destruction.sink().template connect<&descriptor_type::template induce_if<1>>(curr), ...);
-                (sighs[type<Exclude>()].construction.sink().template connect<&descriptor_type::discard_if>(curr), ...);
+                auto owned = {((it = handler(type<Owned>()),
+                        it->construction.sink().template connect<&descriptor_type::template induce_if<0>>(curr),
+                        it->destruction.sink().template connect<&descriptor_type::discard_if>(curr), it->pool.get()), ...)};
 
-                const auto *cpool = std::min({ pools[type<Owned>()].get()... }, [](const auto *lhs, const auto *rhs) {
+                const auto *cpool = std::min(owned, [](const auto *lhs, const auto *rhs) {
                     return lhs->size() < rhs->size();
                 });
 
@@ -1278,7 +1321,8 @@ public:
         std::vector<const sparse_set<Entity> *> set(last - first);
 
         std::transform(first, last, set.begin(), [this](const component_type ctype) {
-            return ctype < pools.size() ? pools[ctype].get() : nullptr;
+            auto pdata = handler(ctype);
+            return pdata != pools.cend() && pdata->pool ? pdata->pool.get() : nullptr;
         });
 
         return { std::move(set) };
@@ -1314,24 +1358,20 @@ public:
      */
     template<typename... Component>
     registry clone() const {
+        static_assert(std::conjunction_v<std::is_copy_constructible<Component>...>);
         registry other;
 
         other.pools.resize(pools.size());
-        other.sighs.resize(pools.size());
 
-        if constexpr(sizeof...(Component) == 0) {
-            for(auto pos = pools.size(); pos; --pos) {
-                auto &cpool = pools[pos-1];
+        for(auto pos = pools.size(); pos; --pos) {
+            auto &pdata = pools[pos-1];
 
-                if(cpool) {
-                    other.pools[pos-1] = cpool->clone();
-                    assert(other.pools[pos-1]);
-                }
-            };
-        } else {
-            static_assert(std::conjunction_v<std::is_copy_constructible<Component>...>);
-            ((other.pools[type<Component>()] = assure<Component>().clone()), ...);
-            assert((other.pools[type<Component>()] && ...));
+            if(pdata.pool && (!sizeof...(Component) || ... || (pdata.runtime_type == type<Component>()))) {
+                auto &curr = other.pools[pos-1];
+                curr.pool = pdata.pool->clone();
+                curr.runtime_type = pdata.runtime_type;
+                assert(curr.pool);
+            }
         }
 
         other.next = next;
@@ -1409,8 +1449,7 @@ public:
 
 private:
     std::vector<std::unique_ptr<basic_descriptor>> groups;
-    mutable std::vector<std::unique_ptr<sparse_set<Entity>>> pools;
-    mutable std::vector<signals> sighs;
+    mutable std::vector<pool_data> pools;
     std::vector<entity_type> entities;
     size_type available{};
     entity_type next{};

+ 0 - 3
src/entt/process/process.hpp

@@ -83,9 +83,6 @@ class process {
     template<state value>
     using state_value_t = std::integral_constant<state, value>;
 
-    template<state value>
-    inline static state_value_t<value> state_value{};
-
     template<typename Target = Derived>
     auto tick(int, state_value_t<state::UNINITIALIZED>, void *data)
     -> decltype(std::declval<Target>().init(data)) {

+ 1 - 1
src/entt/signal/delegate.hpp

@@ -54,7 +54,7 @@ struct connect_arg_t {};
 
 /*! @brief Constant of type connect_arg_t used to disambiguate calls. */
 template<auto Func>
-inline static connect_arg_t<Func> connect_arg{};
+constexpr connect_arg_t<Func> connect_arg{};
 
 
 /**

+ 2 - 3
src/entt/signal/emitter.hpp

@@ -6,7 +6,6 @@
 #include <functional>
 #include <algorithm>
 #include <utility>
-#include <cstddef>
 #include <memory>
 #include <vector>
 #include <list>
@@ -117,7 +116,7 @@ class emitter {
 
     template<typename Event>
     event_handler<Event> & handler() ENTT_NOEXCEPT {
-        const std::size_t family = handler_family::type<Event>;
+        const auto family = handler_family::type<Event>;
 
         if(!(family < handlers.size())) {
             handlers.resize(family+1);
@@ -291,7 +290,7 @@ public:
      */
     template<typename Event>
     bool empty() const ENTT_NOEXCEPT {
-        const std::size_t family = handler_family::type<Event>;
+        const auto family = handler_family::type<Event>;
 
         return (!(family < handlers.size()) ||
                 !handlers[family] ||

+ 29 - 5
test/CMakeLists.txt

@@ -5,12 +5,16 @@
 include_directories($<TARGET_PROPERTY:EnTT,INTERFACE_INCLUDE_DIRECTORIES>)
 add_compile_options($<TARGET_PROPERTY:EnTT,INTERFACE_COMPILE_OPTIONS>)
 
+macro(SETUP_LIBRARY_TARGET LIB_TARGET)
+    set_target_properties(${LIB_TARGET} PROPERTIES CXX_EXTENSIONS OFF)
+    target_compile_definitions(${LIB_TARGET} PRIVATE $<TARGET_PROPERTY:EnTT,INTERFACE_COMPILE_DEFINITIONS>)
+    target_compile_features(${LIB_TARGET} PRIVATE $<TARGET_PROPERTY:EnTT,INTERFACE_COMPILE_FEATURES>)
+    target_compile_options(${LIB_TARGET} PRIVATE $<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-pedantic -Wall>)
+    target_compile_options(${LIB_TARGET} PRIVATE $<$<CXX_COMPILER_ID:MSVC>:/EHsc>)
+endmacro()
+
 add_library(odr OBJECT odr.cpp)
-set_target_properties(odr PROPERTIES CXX_EXTENSIONS OFF)
-target_compile_definitions(odr PRIVATE $<TARGET_PROPERTY:EnTT,INTERFACE_COMPILE_DEFINITIONS>)
-target_compile_features(odr PRIVATE $<TARGET_PROPERTY:EnTT,INTERFACE_COMPILE_FEATURES>)
-target_compile_options(odr PRIVATE $<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-pedantic -Wall>)
-target_compile_options(odr PRIVATE $<$<CXX_COMPILER_ID:MSVC>:/EHsc>)
+SETUP_LIBRARY_TARGET(odr)
 
 macro(SETUP_AND_ADD_TEST TEST_NAME TEST_SOURCE)
     add_executable(${TEST_NAME} $<TARGET_OBJECTS:odr> ${TEST_SOURCE})
@@ -29,6 +33,26 @@ if(BUILD_BENCHMARK)
     SETUP_AND_ADD_TEST(benchmark benchmark/benchmark.cpp)
 endif()
 
+# Test lib
+
+if(BUILD_LIB)
+    add_library(a_module_shared SHARED lib/a_module.cpp)
+    add_library(a_module_static STATIC lib/a_module.cpp)
+    add_library(another_module_shared SHARED lib/another_module.cpp)
+    add_library(another_module_static STATIC lib/another_module.cpp)
+
+    SETUP_LIBRARY_TARGET(a_module_shared)
+    SETUP_LIBRARY_TARGET(a_module_static)
+    SETUP_LIBRARY_TARGET(another_module_shared)
+    SETUP_LIBRARY_TARGET(another_module_static)
+
+    SETUP_AND_ADD_TEST(lib_shared lib/lib.cpp)
+    target_link_libraries(lib_shared PRIVATE a_module_shared another_module_shared)
+
+    SETUP_AND_ADD_TEST(lib_static lib/lib.cpp)
+    target_link_libraries(lib_static PRIVATE a_module_static another_module_static)
+endif()
+
 # Test mod
 
 if(BUILD_MOD)

+ 29 - 0
test/lib/a_module.cpp

@@ -0,0 +1,29 @@
+#include <entt/entity/registry.hpp>
+
+#ifndef LIB_EXPORT
+#if defined _WIN32 || defined __CYGWIN__
+#define LIB_EXPORT __declspec(dllexport)
+#elif defined __GNUC__
+#define LIB_EXPORT __attribute__((visibility("default")))
+#else
+#define LIB_EXPORT
+#endif
+#endif
+
+LIB_EXPORT typename entt::registry<>::component_type a_module_int_type() {
+    entt::registry<> registry;
+
+    (void)registry.type<double>();
+    (void)registry.type<float>();
+
+    return registry.type<int>();
+}
+
+LIB_EXPORT typename entt::registry<>::component_type a_module_char_type() {
+    entt::registry<> registry;
+
+    (void)registry.type<double>();
+    (void)registry.type<float>();
+
+    return registry.type<char>();
+}

+ 35 - 0
test/lib/another_module.cpp

@@ -0,0 +1,35 @@
+#include <entt/entity/registry.hpp>
+
+#ifndef LIB_EXPORT
+#if defined _WIN32 || defined __CYGWIN__
+#define LIB_EXPORT __declspec(dllexport)
+#elif defined __GNUC__
+#define LIB_EXPORT __attribute__((visibility("default")))
+#else
+#define LIB_EXPORT
+#endif
+#endif
+
+LIB_EXPORT typename entt::registry<>::component_type another_module_int_type() {
+    entt::registry<> registry;
+
+    (void)registry.type<char>();
+    (void)registry.type<const int>();
+    (void)registry.type<double>();
+    (void)registry.type<const char>();
+    (void)registry.type<float>();
+
+    return registry.type<int>();
+}
+
+LIB_EXPORT typename entt::registry<>::component_type another_module_char_type() {
+    entt::registry<> registry;
+
+    (void)registry.type<int>();
+    (void)registry.type<const char>();
+    (void)registry.type<float>();
+    (void)registry.type<const int>();
+    (void)registry.type<double>();
+
+    return registry.type<char>();
+}

+ 24 - 0
test/lib/lib.cpp

@@ -0,0 +1,24 @@
+#include <entt/entity/registry.hpp>
+#include <gtest/gtest.h>
+
+extern typename entt::registry<>::component_type a_module_int_type();
+extern typename entt::registry<>::component_type a_module_char_type();
+extern typename entt::registry<>::component_type another_module_int_type();
+extern typename entt::registry<>::component_type another_module_char_type();
+
+TEST(Lib, Shared) {
+    entt::registry<> registry;
+
+    ASSERT_EQ(registry.type<int>(), registry.type<const int>());
+    ASSERT_EQ(registry.type<char>(), registry.type<const char>());
+
+    ASSERT_EQ(registry.type<int>(), a_module_int_type());
+    ASSERT_EQ(registry.type<char>(), a_module_char_type());
+    ASSERT_EQ(registry.type<const int>(), a_module_int_type());
+    ASSERT_EQ(registry.type<const char>(), a_module_char_type());
+
+    ASSERT_EQ(registry.type<const char>(), another_module_char_type());
+    ASSERT_EQ(registry.type<const int>(), another_module_int_type());
+    ASSERT_EQ(registry.type<char>(), another_module_char_type());
+    ASSERT_EQ(registry.type<int>(), another_module_int_type());
+}