Explorar o código

meta: barebone support to typeless types

skypjack hai 3 días
pai
achega
c0d2b94243
Modificáronse 4 ficheiros con 635 adicións e 16 borrados
  1. 6 0
      TODO
  2. 260 7
      src/entt/meta/factory.hpp
  3. 35 6
      test/entt/meta/meta_context.cpp
  4. 334 3
      test/entt/meta/meta_factory.cpp

+ 6 - 0
TODO

@@ -35,3 +35,9 @@ TODO:
 * document stl and injections support
 * add instance-type to meta data and func (useful for typeless types)
 * use a dense_set for the meta context bucket to speed up lookups
+* new meta multi support: md and natvis
+* improve meta_reset and similar on multi contexts
+* improve resolve to support multi contexts (ie key == elem.id quick check)
+* use dense_set for meta context bucket to speed up lookups and reduce memory usage
+* review/refine/improve meta factory base for typed vs typeless types (ctor and type)
+* check coverage on new meta context features

+ 260 - 7
src/entt/meta/factory.hpp

@@ -54,7 +54,7 @@ class basic_meta_factory {
 protected:
     void type(const id_type id, const char *name) noexcept {
         state = mode::type;
-        ENTT_ASSERT(parent->id == id || (stl::find_if(ctx->bucket.cbegin(), ctx->bucket.cend(), [id](const auto &value) { return value.second->id == id; }) == ctx->bucket.cend()), "Duplicate identifier");
+        ENTT_ASSERT((parent->id == id) || stl::find_if(ctx->bucket.cbegin(), ctx->bucket.cend(), [id](const auto &value) { return value.second->id == id; }) == ctx->bucket.cend(), "Duplicate identifier");
         parent->name = name;
         parent->id = id;
     }
@@ -133,12 +133,12 @@ protected:
     }
 
 public:
-    basic_meta_factory(meta_ctx &area, meta_type_node node)
+    basic_meta_factory(meta_ctx &area, meta_type_node node, const id_type id)
         : ctx{&meta_context::from(area)},
-          bucket{node.info->hash()},
+          bucket{},
           state{mode::type} {
-        if(const auto it = ctx->bucket.find(bucket); it == ctx->bucket.cend()) {
-            parent = ctx->bucket.emplace(bucket, stl::make_unique<meta_type_node>(stl::move(node))).first->second.get();
+        if(const auto it = ctx->bucket.find(id); it == ctx->bucket.cend()) {
+            parent = ctx->bucket.emplace(id, stl::make_unique<meta_type_node>(stl::move(node))).first->second.get();
             parent->details = stl::make_unique<meta_type_descriptor>();
         } else {
             parent = it->second.get();
@@ -147,9 +147,9 @@ public:
 
 private:
     meta_context *ctx{};
-    id_type bucket{};
     invoke_type *invoke{};
     meta_type_node *parent{};
+    id_type bucket{};
     mode state{};
 };
 
@@ -177,7 +177,7 @@ public:
      * @param area The context into which to construct meta types.
      */
     meta_factory(meta_ctx &area) noexcept
-        : internal::basic_meta_factory{area, internal::setup_node_for<element_type>()} {}
+        : internal::basic_meta_factory{area, internal::setup_node_for<element_type>(), type_hash<Type>::value()} {}
 
     /**
      * @brief Assigns a custom unique identifier to a meta type.
@@ -545,6 +545,259 @@ public:
     }
 };
 
+/*! @brief Meta factory specialization for typeless meta types. */
+template<>
+class meta_factory<void>: private internal::basic_meta_factory {
+    using base_type = internal::basic_meta_factory;
+
+public:
+    /*! @brief Type of object for which this factory builds a meta type. */
+    using element_type = void;
+
+    /**
+     * @brief Constructs a factory for a uniquely identified typeless type.
+     * @param name A custom unique identifier as a **string literal**.
+     */
+    meta_factory(const char *name) noexcept
+        : meta_factory{locator<meta_ctx>::value_or(), name} {}
+
+    /**
+     * @brief Constructs a factory for a uniquely identified typeless type.
+     * @param id A custom unique identifier.
+     * @param name An optional name for the type as a **string literal**.
+     */
+    meta_factory(const id_type id, const char *name = nullptr) noexcept
+        : meta_factory{locator<meta_ctx>::value_or(), id, name} {}
+
+    /**
+     * @brief Context aware constructor for uniquely identified typeless types.
+     * @param name A custom unique identifier as a **string literal**.
+     */
+    meta_factory(meta_ctx &area, const char *name) noexcept
+        : meta_factory{area, hashed_string::value(name), name} {}
+
+    /**
+     * @brief Context aware constructor for uniquely identified typeless types.
+     * @param id A custom unique identifier.
+     * @param name An optional name for the type as a **string literal**.
+     */
+    meta_factory(meta_ctx &area, const id_type id, const char *name = nullptr) noexcept
+        : internal::basic_meta_factory{area, internal::setup_node_for<element_type>(), id} {
+        type(id, name);
+    }
+
+    /**
+     * @brief Assigns a meta data to a meta type.
+     * @tparam Data The actual variable to attach to the meta type.
+     * @tparam Policy Optional policy (no policy set by default).
+     * @param name A custom unique identifier as a **string literal**.
+     * @return A meta factory for the given type.
+     */
+    template<auto Data, typename Policy = as_value_t>
+    meta_factory data(const char *name) noexcept {
+        return data<Data, Policy>(hashed_string::value(name), name);
+    }
+
+    /**
+     * @brief Assigns a meta data to a meta type.
+     *
+     * Both data members and static and global variables, as well as constants
+     * of any kind, can be assigned to a meta type.<br/>
+     * From a client's point of view, all the variables associated with the
+     * reflected object will appear as if they were part of the type itself.
+     *
+     * @tparam Data The actual variable to attach to the meta type.
+     * @tparam Policy Optional policy (no policy set by default).
+     * @param id Unique identifier.
+     * @param name An optional name for the meta data as a **string literal**.
+     * @return A meta factory for the parent type.
+     */
+    template<auto Data, typename Policy = as_value_t>
+    meta_factory data(const id_type id, const char *name = nullptr) noexcept {
+        if constexpr(stl::is_member_object_pointer_v<decltype(Data)>) {
+            using class_type = member_class_t<decltype(Data)>;
+            using data_type = stl::invoke_result_t<decltype(Data), class_type &>;
+            static_assert(Policy::template value<data_type>, "Invalid return type for the given policy");
+
+            base_type::data(
+                internal::meta_data_node{
+                    id,
+                    name,
+                    /* this is never static */
+                    stl::is_const_v<stl::remove_reference_t<data_type>> ? internal::meta_traits::is_const : internal::meta_traits::is_none,
+                    &internal::resolve<stl::remove_cvref_t<data_type>>,
+                    &internal::resolve<stl::remove_cvref_t<data_type>>,
+                    &meta_setter<class_type, Data>,
+                    &meta_getter<class_type, Data, Policy>});
+        } else {
+            using data_type = stl::remove_pointer_t<decltype(Data)>;
+
+            if constexpr(stl::is_pointer_v<decltype(Data)>) {
+                static_assert(Policy::template value<decltype(*Data)>, "Invalid return type for the given policy");
+            } else {
+                static_assert(Policy::template value<data_type>, "Invalid return type for the given policy");
+            }
+
+            base_type::data(
+                internal::meta_data_node{
+                    id,
+                    name,
+                    ((!stl::is_pointer_v<decltype(Data)> || stl::is_const_v<data_type>) ? internal::meta_traits::is_const : internal::meta_traits::is_none) | internal::meta_traits::is_static,
+                    &internal::resolve<stl::remove_cvref_t<data_type>>,
+                    &internal::resolve<stl::remove_cvref_t<data_type>>,
+                    &meta_setter<void, Data>,
+                    &meta_getter<void, Data, Policy>});
+        }
+
+        return *this;
+    }
+
+    /**
+     * @brief Assigns a meta data to a meta type by means of its setter and
+     * getter.
+     * @tparam Setter The actual function to use as a setter.
+     * @tparam Getter The actual function to use as a getter.
+     * @tparam Policy Optional policy (no policy set by default).
+     * @param name A custom unique identifier as a **string literal**.
+     * @return A meta factory for the given type.
+     */
+    template<auto Setter, auto Getter, typename Policy = as_value_t>
+    meta_factory data(const char *name) noexcept {
+        return data<Setter, Getter, Policy>(hashed_string::value(name), name);
+    }
+
+    /**
+     * @brief Assigns a meta data to a meta type by means of its setter and
+     * getter.
+     *
+     * Setters and getters can be either free functions, member functions or a
+     * mix of them.<br/>
+     * In case of free functions, setters and getters must accept a reference to
+     * an instance of the parent type as their first argument. A setter has then
+     * an extra argument of a type convertible to that of the parameter to
+     * set.<br/>
+     * In case of member functions, getters have no arguments at all, while
+     * setters has an argument of a type convertible to that of the parameter to
+     * set.
+     *
+     * @tparam Setter The actual function to use as a setter.
+     * @tparam Getter The actual function to use as a getter.
+     * @tparam Policy Optional policy (no policy set by default).
+     * @param id Unique identifier.
+     * @param name An optional name for the meta data as a **string literal**.
+     * @return A meta factory for the parent type.
+     */
+    template<auto Setter, auto Getter, typename Policy = as_value_t>
+    meta_factory data(const id_type id, const char *name = nullptr) noexcept {
+        using class_type = typename stl::conditional_t<stl::is_member_pointer_v<decltype(Getter)>, member_class<decltype(Getter)>, stl::type_identity<void>>::type;
+        using descriptor = meta_function_helper_t<class_type, decltype(Getter)>;
+        static_assert(Policy::template value<typename descriptor::return_type>, "Invalid return type for the given policy");
+
+        if constexpr(stl::is_same_v<decltype(Setter), stl::nullptr_t>) {
+            base_type::data(
+                internal::meta_data_node{
+                    id,
+                    name,
+                    /* this is never static */
+                    internal::meta_traits::is_const,
+                    &internal::resolve<stl::remove_cvref_t<typename descriptor::return_type>>,
+                    nullptr,
+                    &meta_setter<class_type, Setter>,
+                    &meta_getter<class_type, Getter, Policy>});
+        } else {
+            using args_type = meta_function_helper_t<class_type, decltype(Setter)>::args_type;
+
+            base_type::data(
+                internal::meta_data_node{
+                    id,
+                    name,
+                    /* this is never static nor const */
+                    internal::meta_traits::is_none,
+                    &internal::resolve<stl::remove_cvref_t<typename descriptor::return_type>>,
+                    &internal::resolve<stl::remove_cvref_t<type_list_element_t<static_cast<stl::size_t>(args_type::size != 1u), args_type>>>,
+                    &meta_setter<class_type, Setter>,
+                    &meta_getter<class_type, Getter, Policy>});
+        }
+
+        return *this;
+    }
+
+    /**
+     * @brief Assigns a meta function to a meta type.
+     * @tparam Candidate The actual function to attach to the meta function.
+     * @tparam Policy Optional policy (no policy set by default).
+     * @param name A custom unique identifier as a **string literal**.
+     * @return A meta factory for the given type.
+     */
+    template<auto Candidate, typename Policy = as_value_t>
+    meta_factory func(const char *name) noexcept {
+        return func<Candidate, Policy>(hashed_string::value(name), name);
+    }
+
+    /**
+     * @brief Assigns a meta function to a meta type.
+     *
+     * Both member functions and free functions can be assigned to a meta
+     * type.<br/>
+     * From a client's point of view, all the functions associated with the
+     * reflected object will appear as if they were part of the type itself.
+     *
+     * @tparam Candidate The actual function to attach to the meta type.
+     * @tparam Policy Optional policy (no policy set by default).
+     * @param id Unique identifier.
+     * @param name An optional name for the function as a **string literal**.
+     * @return A meta factory for the parent type.
+     */
+    template<auto Candidate, typename Policy = as_value_t>
+    meta_factory func(const id_type id, const char *name = nullptr) noexcept {
+        using class_type = typename stl::conditional_t<stl::is_member_pointer_v<decltype(Candidate)>, member_class<decltype(Candidate)>, stl::type_identity<void>>::type;
+        using descriptor = meta_function_helper_t<class_type, decltype(Candidate)>;
+        static_assert(Policy::template value<typename descriptor::return_type>, "Invalid return type for the given policy");
+
+        base_type::func(
+            internal::meta_func_node{
+                id,
+                name,
+                (descriptor::is_const ? internal::meta_traits::is_const : internal::meta_traits::is_none) | (descriptor::is_static ? internal::meta_traits::is_static : internal::meta_traits::is_none),
+                descriptor::args_type::size,
+                &internal::resolve<stl::conditional_t<stl::is_same_v<Policy, as_void_t>, void, stl::remove_cvref_t<typename descriptor::return_type>>>,
+                &meta_arg<typename descriptor::args_type>,
+                &meta_invoke<class_type, Candidate, Policy>});
+
+        return *this;
+    }
+
+    /**
+     * @brief Sets traits on the last created meta object.
+     *
+     * The assigned value must be an enum and intended as a bitmask.
+     *
+     * @tparam Value Type of the traits value.
+     * @param value Traits value.
+     * @param unset True to unset the given traits, false otherwise.
+     * @return A meta factory for the parent type.
+     */
+    template<typename Value>
+    meta_factory traits(const Value value, const bool unset = false) {
+        static_assert(stl::is_enum_v<Value>, "Invalid enum type");
+        base_type::traits(internal::user_to_meta_traits(value), unset);
+        return *this;
+    }
+
+    /**
+     * @brief Sets user defined data that will never be used by the library.
+     * @tparam Value Type of user defined data to store.
+     * @tparam Args Types of arguments to use to construct the user data.
+     * @param args Parameters to use to initialize the user data.
+     * @return A meta factory for the parent type.
+     */
+    template<typename Value, typename... Args>
+    meta_factory custom(Args &&...args) {
+        base_type::custom(internal::meta_custom_node{type_id<Value>().hash(), stl::make_shared<Value>(stl::forward<Args>(args)...)});
+        return *this;
+    }
+};
+
 /**
  * @brief Resets a type and all its parts.
  *

+ 35 - 6
test/entt/meta/meta_context.cpp

@@ -34,6 +34,9 @@ class MetaContext: public ::testing::Test {
 
         entt::meta_factory<template_clazz<int>>{}
             .type("template"_hs);
+
+        entt::meta_factory<void>("global")
+            .data<1>("value"_hs);
     }
 
     void init_local_context() {
@@ -63,6 +66,9 @@ class MetaContext: public ::testing::Test {
 
         entt::meta_factory<template_clazz<int, char>>{context}
             .type("template"_hs);
+
+        entt::meta_factory<void>(context, "local")
+            .data<2>("value"_hs);
     }
 
 public:
@@ -175,8 +181,14 @@ TEST_F(MetaContext, Resolve) {
     ASSERT_FALSE(entt::resolve("quux"_hs));
     ASSERT_TRUE(entt::resolve(ctx(), "quux"_hs));
 
-    ASSERT_EQ((std::distance(entt::resolve().cbegin(), entt::resolve().cend())), 4);
-    ASSERT_EQ((std::distance(entt::resolve(ctx()).cbegin(), entt::resolve(ctx()).cend())), 6);
+    ASSERT_TRUE(entt::resolve("global"_hs));
+    ASSERT_FALSE(entt::resolve(ctx(), "global"_hs));
+
+    ASSERT_FALSE(entt::resolve("local"_hs));
+    ASSERT_TRUE(entt::resolve(ctx(), "local"_hs));
+
+    ASSERT_EQ((std::distance(entt::resolve().cbegin(), entt::resolve().cend())), 5);
+    ASSERT_EQ((std::distance(entt::resolve(ctx()).cbegin(), entt::resolve(ctx()).cend())), 7);
 }
 
 TEST_F(MetaContext, MetaType) {
@@ -211,6 +223,27 @@ TEST_F(MetaContext, MetaType) {
     ASSERT_EQ(local.invoke("get"_hs, instance).cast<char>(), 'c');
 }
 
+TEST_F(MetaContext, MetaTypelessType) {
+    using namespace entt::literals;
+
+    const auto global = entt::resolve("global"_hs);
+    const auto local = entt::resolve(ctx(), "local"_hs);
+
+    ASSERT_TRUE(global);
+    ASSERT_TRUE(local);
+
+    ASSERT_NE(global, local);
+
+    ASSERT_EQ(global.id(), "global"_hs);
+    ASSERT_EQ(local.id(), "local"_hs);
+
+    clazz instance{'c', 8};
+    const argument value{2};
+
+    ASSERT_EQ(global.get("value"_hs, {}).cast<int>(), 1);
+    ASSERT_EQ(local.get("value"_hs, {}).cast<int>(), 2);
+}
+
 TEST_F(MetaContext, MetaBase) {
     const auto global = entt::resolve<clazz>();
     const auto local = entt::resolve<clazz>(ctx());
@@ -484,7 +517,3 @@ TEST_F(MetaContext, ForwardAsMeta) {
     ASSERT_EQ(global.type().data("marker"_hs).get({}).cast<int>(), global_marker);
     ASSERT_EQ(local.type().data("marker"_hs).get({}).cast<int>(), local_marker);
 }
-
-TEST_F(MetaContext, Typeless) {
-    // work in progress ...
-}

+ 334 - 3
test/entt/meta/meta_factory.cpp

@@ -4,6 +4,7 @@
 #include <utility>
 #include <gtest/gtest.h>
 #include <entt/core/type_info.hpp>
+#include <entt/locator/locator.hpp>
 #include <entt/meta/context.hpp>
 #include <entt/meta/factory.hpp>
 #include <entt/meta/meta.hpp>
@@ -55,6 +56,7 @@ struct MetaFactory: ::testing::Test {
     }
 };
 
+using MetaVoidFactory = MetaFactory;
 using MetaFactoryDeathTest = MetaFactory;
 
 TEST_F(MetaFactory, Constructors) {
@@ -493,8 +495,337 @@ TEST_F(MetaFactory, MetaReset) {
     ASSERT_FALSE(entt::resolve(ctx, entt::type_id<int>()));
 }
 
-TEST_F(MetaFactory, Void) {
-    entt::meta_factory<void> factory{};
+TEST_F(MetaVoidFactory, Constructors) {
+    using namespace entt::literals;
+
+    entt::meta_ctx ctx{};
+
+    ASSERT_FALSE(entt::resolve("type"_hs));
+    ASSERT_FALSE(entt::resolve(ctx, "type"_hs));
+
+    entt::meta_factory<void> factory{"type"};
+
+    ASSERT_TRUE(entt::resolve("type"_hs));
+    ASSERT_FALSE(entt::resolve(ctx, "type"_hs));
+    ASSERT_EQ(entt::resolve("type"_hs).info(), entt::type_id<void>());
+
+    factory = entt::meta_factory<void>{ctx, "type"};
+
+    ASSERT_TRUE(entt::resolve("type"_hs));
+    ASSERT_TRUE(entt::resolve(ctx, "type"_hs));
+    ASSERT_EQ(entt::resolve(ctx, "type"_hs).info(), entt::type_id<void>());
+}
+
+TEST_F(MetaVoidFactory, DataMemberObject) {
+    using namespace entt::literals;
+
+    base instance{'c'};
+    entt::meta_factory<void> factory{"type"};
+    entt::meta_type type = entt::resolve("type"_hs);
+
+    ASSERT_FALSE(type.data("member"_hs));
+
+    factory.data<&base::member>("member"_hs);
+    type = entt::resolve("type"_hs);
+
+    ASSERT_TRUE(type.data("member"_hs));
+    ASSERT_EQ(type.get("member"_hs, std::as_const(instance)), instance.member);
+    ASSERT_EQ(type.get("member"_hs, instance), instance.member);
+    ASSERT_FALSE(type.set("member"_hs, std::as_const(instance), instance.member));
+    ASSERT_TRUE(type.set("member"_hs, instance, instance.member));
+}
+
+TEST_F(MetaVoidFactory, DataPointer) {
+    using namespace entt::literals;
+
+    entt::meta_factory<void> factory{"type"};
+    entt::meta_type type = entt::resolve("type"_hs);
+
+    ASSERT_FALSE(type.data("value"_hs));
+
+    static int value = 1;
+    factory.data<&value>("value"_hs);
+    type = entt::resolve("type"_hs);
+
+    ASSERT_TRUE(type.data("value"_hs));
+    ASSERT_EQ(type.get("value"_hs, {}), value);
+    ASSERT_TRUE(type.set("value"_hs, {}, value));
+}
+
+TEST_F(MetaVoidFactory, DataValue) {
+    using namespace entt::literals;
+
+    constexpr int value = 1;
+    entt::meta_factory<void> factory{"type"};
+    entt::meta_type type = entt::resolve("type"_hs);
+
+    ASSERT_FALSE(type.data("value"_hs));
+
+    factory.data<value>("value"_hs);
+    type = entt::resolve("type"_hs);
+
+    ASSERT_TRUE(type.data("value"_hs));
+    ASSERT_EQ(type.get("value"_hs, {}), value);
+    ASSERT_FALSE(type.set("value"_hs, {}, value));
+}
+
+TEST_F(MetaVoidFactory, DataGetterOnly) {
+    using namespace entt::literals;
+
+    clazz instance{1};
+    entt::meta_factory<void> factory{"type"};
+    entt::meta_type type = entt::resolve("type"_hs);
+
+    ASSERT_FALSE(type.data("value"_hs));
+
+    factory.data<nullptr, &clazz::get_int>("value"_hs);
+    type = entt::resolve("type"_hs);
+
+    ASSERT_TRUE(type.data("value"_hs));
+    ASSERT_EQ(type.get("value"_hs, std::as_const(instance)), instance.get_int());
+    ASSERT_EQ(type.get("value"_hs, instance), instance.get_int());
+    ASSERT_FALSE(type.set("value"_hs, std::as_const(instance), instance.get_int()));
+    ASSERT_FALSE(type.set("value"_hs, instance, instance.get_int()));
+}
+
+TEST_F(MetaVoidFactory, DataSetterGetter) {
+    using namespace entt::literals;
+
+    clazz instance{1};
+    entt::meta_factory<void> factory{"type"};
+    entt::meta_type type = entt::resolve("type"_hs);
+
+    ASSERT_FALSE(type.data("value"_hs));
+
+    factory.data<&clazz::set_int, &clazz::get_int>("value"_hs);
+    type = entt::resolve("type"_hs);
+
+    ASSERT_TRUE(type.data("value"_hs));
+    ASSERT_EQ(type.get("value"_hs, std::as_const(instance)), instance.get_int());
+    ASSERT_EQ(type.get("value"_hs, instance), instance.get_int());
+    ASSERT_FALSE(type.set("value"_hs, std::as_const(instance), instance.get_int()));
+    ASSERT_TRUE(type.set("value"_hs, instance, instance.get_int()));
+}
+
+TEST_F(MetaVoidFactory, DataOverwrite) {
+    using namespace entt::literals;
+
+    entt::meta_factory<void> factory{"type"};
+    entt::meta_type type = entt::resolve("type"_hs);
+
+    ASSERT_FALSE(type.data("value"_hs));
+
+    factory.data<nullptr, &clazz::get_int>("value"_hs);
+    type = entt::resolve("type"_hs);
+
+    ASSERT_TRUE(type.data("value"_hs));
+    ASSERT_TRUE(type.data("value"_hs).is_const());
+
+    factory.data<&clazz::set_int, &clazz::get_int>("value"_hs);
+    type = entt::resolve("type"_hs);
+
+    ASSERT_TRUE(type.data("value"_hs));
+    ASSERT_FALSE(type.data("value"_hs).is_const());
+}
+
+TEST_F(MetaVoidFactory, DataTypeNameClash) {
+    using namespace entt::literals;
+
+    const auto value = 2;
+    entt::meta_factory<void> factory{"type"};
+    const auto member = entt::type_id<base>().hash();
+    entt::meta_data data = entt::resolve("type"_hs).data(member);
+
+    ASSERT_FALSE(data);
+
+    factory.data<&base::member>(member).custom<int>(value);
+    data = entt::resolve("type"_hs).data(member);
+
+    ASSERT_TRUE(data);
+    ASSERT_NE(static_cast<const int *>(data.custom()), nullptr);
+    ASSERT_EQ(static_cast<int>(data.custom()), value);
+}
+
+TEST_F(MetaVoidFactory, Func) {
+    using namespace entt::literals;
+
+    const clazz instance{1};
+    entt::meta_factory<void> factory{"type"};
+    entt::meta_type type = entt::resolve("type"_hs);
+
+    ASSERT_FALSE(type.func("func"_hs));
+
+    factory.func<&clazz::get_int>("func"_hs);
+    type = entt::resolve("type"_hs);
+
+    ASSERT_TRUE(type.func("func"_hs));
+    ASSERT_TRUE(type.invoke("func"_hs, instance));
+    ASSERT_EQ(type.invoke("func"_hs, instance).cast<int>(), instance.get_int());
+    ASSERT_FALSE(type.invoke("func"_hs, {}));
+}
+
+TEST_F(MetaVoidFactory, FuncOverload) {
+    using namespace entt::literals;
+
+    clazz instance{1};
+    entt::meta_factory<void> factory{"type"};
+    entt::meta_type type = entt::resolve("type"_hs);
+
+    ASSERT_FALSE(type.func("func"_hs));
+
+    factory.func<&clazz::set_int>("func"_hs);
+
+    ASSERT_TRUE(type.func("func"_hs));
+    ASSERT_FALSE(type.func("func"_hs).next());
+
+    factory.func<&clazz::set_boxed_int>("func"_hs);
+
+    ASSERT_TRUE(type.func("func"_hs));
+    ASSERT_TRUE(type.func("func"_hs).next());
+    ASSERT_FALSE(type.func("func"_hs).next().next());
+
+    ASSERT_TRUE(type.invoke("func"_hs, instance, 2));
+    ASSERT_EQ(instance.get_int(), 2);
+
+    ASSERT_TRUE(type.invoke("func"_hs, instance, test::boxed_int{3}));
+    ASSERT_EQ(instance.get_int(), 3);
+}
+
+TEST_F(MetaVoidFactory, FuncTypeNameClash) {
+    using namespace entt::literals;
+
+    const auto value = 2;
+    const auto name = "type"_hs;
+    entt::meta_factory<void> factory{name};
+    entt::meta_func func = entt::resolve(name).func(name);
+
+    ASSERT_FALSE(func);
+
+    factory.func<&clazz::get_int>(name).custom<int>(value);
+    func = entt::resolve(name).func(name);
+
+    ASSERT_TRUE(func);
+    ASSERT_NE(static_cast<const int *>(func.custom()), nullptr);
+    ASSERT_EQ(static_cast<int>(func.custom()), value);
+}
+
+TEST_F(MetaVoidFactory, Traits) {
+    using namespace entt::literals;
+
+    entt::meta_factory<void>{"type"}
+        .data<&base::member>("member"_hs)
+        .func<&clazz::set_int>("func"_hs)
+        .func<&clazz::set_boxed_int>("func"_hs);
+
+    entt::meta_type type = entt::resolve("type"_hs);
+
+    ASSERT_EQ(type.traits<test::meta_traits>(), test::meta_traits::none);
+    ASSERT_EQ(type.data("member"_hs).traits<test::meta_traits>(), test::meta_traits::none);
+    ASSERT_EQ(type.func("func"_hs).traits<test::meta_traits>(), test::meta_traits::none);
+    ASSERT_EQ(type.func("func"_hs).next().traits<test::meta_traits>(), test::meta_traits::none);
+
+    entt::meta_factory<void>{"type"}
+        .traits(test::meta_traits::one | test::meta_traits::three)
+        .data<&base::member>("member"_hs)
+        .traits(test::meta_traits::one)
+        .func<&clazz::set_int>("func"_hs)
+        .traits(test::meta_traits::two)
+        .func<&clazz::set_boxed_int>("func"_hs)
+        .traits(test::meta_traits::three);
+
+    // traits are copied and never refreshed
+    type = entt::resolve("type"_hs);
+
+    ASSERT_EQ(type.traits<test::meta_traits>(), test::meta_traits::one | test::meta_traits::three);
+    ASSERT_EQ(type.data("member"_hs).traits<test::meta_traits>(), test::meta_traits::one);
+    ASSERT_EQ(type.func("func"_hs).traits<test::meta_traits>(), test::meta_traits::two);
+    ASSERT_EQ(type.func("func"_hs).next().traits<test::meta_traits>(), test::meta_traits::three);
+
+    entt::meta_factory<void>{"type"}
+        .traits(test::meta_traits::one, true)
+        .data<&base::member>("member"_hs)
+        .traits(test::meta_traits::one | test::meta_traits::three, true)
+        .func<&clazz::set_int>("func"_hs)
+        .traits(test::meta_traits::one | test::meta_traits::three, true)
+        .func<&clazz::set_boxed_int>("func"_hs)
+        .traits(test::meta_traits::all, true);
+
+    // traits are copied and never refreshed
+    type = entt::resolve("type"_hs);
+
+    ASSERT_EQ(type.traits<test::meta_traits>(), test::meta_traits::three);
+    ASSERT_EQ(type.data("member"_hs).traits<test::meta_traits>(), test::meta_traits::none);
+    ASSERT_EQ(type.func("func"_hs).traits<test::meta_traits>(), test::meta_traits::two);
+    ASSERT_EQ(type.func("func"_hs).next().traits<test::meta_traits>(), test::meta_traits::none);
+}
+
+TEST_F(MetaVoidFactory, Custom) {
+    using namespace entt::literals;
+
+    entt::meta_factory<void>{"type"}
+        .data<&base::member>("member"_hs)
+        .func<&clazz::set_int>("func"_hs)
+        .func<&clazz::set_boxed_int>("func"_hs);
+
+    entt::meta_type type = entt::resolve("type"_hs);
+
+    ASSERT_EQ(static_cast<const int *>(type.custom()), nullptr);
+    ASSERT_EQ(static_cast<const int *>(type.data("member"_hs).custom()), nullptr);
+    ASSERT_EQ(static_cast<const int *>(type.func("func"_hs).custom()), nullptr);
+    ASSERT_EQ(static_cast<const int *>(type.func("func"_hs).next().custom()), nullptr);
+
+    entt::meta_factory<void>{"type"}
+        .custom<int>(0)
+        .data<&base::member>("member"_hs)
+        .custom<int>(1)
+        .func<&clazz::set_int>("func"_hs)
+        .custom<int>(2)
+        .func<&clazz::set_boxed_int>("func"_hs)
+        .custom<int>(3);
+
+    // custom data pointers are copied and never refreshed
+    type = entt::resolve("type"_hs);
+
+    ASSERT_EQ(static_cast<int>(type.custom()), 0);
+    ASSERT_EQ(static_cast<int>(type.data("member"_hs).custom()), 1);
+    ASSERT_EQ(static_cast<int>(type.func("func"_hs).custom()), 2);
+    ASSERT_EQ(static_cast<int>(type.func("func"_hs).next().custom()), 3);
+}
+
+TEST_F(MetaVoidFactory, MetaReset) {
+    using namespace entt::literals;
+
+    entt::meta_ctx ctx{};
+
+    entt::meta_factory<void>{"type"};
+    entt::meta_factory<void>{ctx, "type"};
+
+    ASSERT_TRUE(entt::resolve("type"_hs));
+    ASSERT_TRUE(entt::resolve(ctx, "type"_hs));
+
+    entt::meta_reset();
+
+    ASSERT_FALSE(entt::resolve("type"_hs));
+    ASSERT_TRUE(entt::resolve(ctx, "type"_hs));
+
+    entt::meta_reset(ctx);
+
+    ASSERT_FALSE(entt::resolve("type"_hs));
+    ASSERT_FALSE(entt::resolve(ctx, "type"_hs));
+
+    entt::meta_factory<void>{"type"};
+    entt::meta_factory<void>{ctx, "type"};
+
+    ASSERT_TRUE(entt::resolve("type"_hs));
+    ASSERT_TRUE(entt::resolve(ctx, "type"_hs));
+
+    entt::meta_reset("type"_hs);
+
+    ASSERT_FALSE(entt::resolve("type"_hs));
+    ASSERT_TRUE(entt::resolve(ctx, "type"_hs));
+
+    entt::meta_reset(ctx, "type"_hs);
 
-    // work in progress ...
+    ASSERT_FALSE(entt::resolve("type"_hs));
+    ASSERT_FALSE(entt::resolve(ctx, "type"_hs));
 }