Răsfoiți Sursa

meta: support for setters/getters

Michele Caini 7 ani în urmă
părinte
comite
66e7141415
4 a modificat fișierele cu 160 adăugiri și 16 ștergeri
  1. 5 1
      docs/meta.md
  2. 51 2
      src/entt/meta/factory.hpp
  3. 41 13
      src/entt/meta/meta.hpp
  4. 63 0
      test/entt/meta/meta.cpp

+ 5 - 1
docs/meta.md

@@ -85,7 +85,11 @@ It can be used to extend the reflected type and add the following:
 
   This function requires as an argument the name to give to the meta data once
   created. Users can then access meta data at runtime by searching for them by
-  name.
+  name.<br/>
+  Data members can be set also by means of a couple of functions, namely a
+  setter and a getter. Setters and getters can be either free functions, member
+  functions or mixed ones, as long as they respect the required signatures.<br/>
+  Refer to the inline documentation for all the details.
 
 * _Member functions_. Both real member functions of the underlying type and free
   functions can be attached to a meta type. From a client's point of view, all

+ 51 - 2
src/entt/meta/factory.hpp

@@ -281,8 +281,8 @@ public:
      * @return A meta factory for the parent type.
      */
     template<auto *Func>
-    std::enable_if_t<std::is_invocable_v<decltype(Func), Type &>, meta_factory &>
-    dtor() ENTT_NOEXCEPT {
+    meta_factory & dtor() ENTT_NOEXCEPT {
+        static_assert(std::is_invocable_v<decltype(Func), Type &>);
         auto * const type = internal::meta_info<Type>::resolve();
 
         static internal::meta_dtor_node node{
@@ -368,6 +368,55 @@ public:
         return *this;
     }
 
+    /**
+     * @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 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 Property Types of properties to assign to the meta data.
+     * @param str The name to assign to the meta data.
+     * @param property Properties to assign to the meta data.
+     * @return A meta factory for the parent type.
+     */
+    template<auto Setter, auto Getter, typename... Property>
+    meta_factory & data(const char *str, Property &&... property) ENTT_NOEXCEPT {
+        using data_type = std::invoke_result_t<decltype(Getter), Type &>;
+        static_assert(std::is_invocable_v<decltype(Setter), Type &, data_type>);
+        auto * const type = internal::meta_info<Type>::resolve();
+
+        static internal::meta_data_node node{
+            hashed_string{str},
+            type->data,
+            type,
+            properties<std::tuple<decltype(Setter), decltype(Getter)>>(std::forward<Property>(property)...),
+            false,
+            false,
+            &internal::meta_info<data_type>::resolve,
+            &internal::setter<false, Type, Setter>,
+            &internal::getter<Type, Getter>,
+            []() -> meta_data {
+                return &node;
+            }
+        };
+
+        assert(!duplicate(hashed_string{str}, node.next));
+        assert((!internal::meta_info<Type>::template data<Setter, Getter>));
+        internal::meta_info<Type>::template data<Setter, Getter> = &node;
+        type->data = &node;
+
+        return *this;
+    }
+
     /**
      * @brief Assigns a meta funcion to a meta type.
      *

+ 41 - 13
src/entt/meta/meta.hpp

@@ -163,7 +163,7 @@ struct meta_node<Type> {
     template<auto>
     inline static meta_dtor_node *dtor = nullptr;
 
-    template<auto>
+    template<auto...>
     inline static meta_data_node *data = nullptr;
 
     template<auto>
@@ -2056,18 +2056,38 @@ template<bool Const, typename Type, auto Data>
 bool setter([[maybe_unused]] meta_handle handle, [[maybe_unused]] meta_any &any) {
     if constexpr(Const) {
         return false;
-    } else if constexpr(std::is_member_object_pointer_v<decltype(Data)>) {
-        using data_type = std::decay_t<decltype(std::declval<Type>().*Data)>;
-        static_assert(std::is_invocable_v<decltype(Data), Type>);
-        const bool accepted = any.can_cast<data_type>() || any.convert<data_type>();
-        auto *clazz = handle.try_cast<Type>();
+    } else if constexpr(std::is_function_v<std::remove_pointer_t<decltype(Data)>> || std::is_member_pointer_v<decltype(Data)>) {
+        auto execute = [&handle, &any](auto *tag) {
+            using data_type = std::remove_pointer_t<decltype(tag)>;
+            const bool accepted = any.can_cast<data_type>() || any.convert<data_type>();
+            auto *clazz = handle.try_cast<Type>();
+
+            if(accepted && clazz) {
+                if constexpr(std::is_function_v<std::remove_pointer_t<decltype(Data)>>) {
+                    Data(*clazz, any.cast<data_type>());
+                } else if constexpr(std::is_member_function_pointer_v<decltype(Data)>) {
+                    (clazz->*Data)(any.cast<data_type>());
+                } else /* if constexpr(std::is_member_object_pointer_v<decltype(Data)>) */ {
+                    clazz->*Data = any.cast<data_type>();
+                }
+            }
 
-        if(accepted && clazz) {
-            clazz->*Data = any.cast<data_type>();
-        }
+            return accepted;
+        };
 
-        return accepted;
+        if constexpr(std::is_function_v<std::remove_pointer_t<decltype(Data)>> || std::is_member_function_pointer_v<decltype(Data)>) {
+            using helper_type = meta_function_helper<std::integral_constant<decltype(Data), Data>>;
+            using data_type = std::tuple_element_t<!std::is_member_function_pointer_v<decltype(Data)>, typename helper_type::args_type>;
+            data_type *tag = nullptr;
+            return execute(tag);
+        } else /* if constexpr(std::is_member_object_pointer_v<decltype(Data)>) */ {
+            using data_type = std::decay_t<decltype(std::declval<Type>().*Data)>;
+            static_assert(std::is_invocable_v<decltype(Data), Type>);
+            data_type *tag = nullptr;
+            return execute(tag);
+        }
     } else {
+        static_assert(std::is_pointer_v<decltype(Data)>);
         using data_type = std::decay_t<decltype(*Data)>;
         const bool accepted = any.can_cast<data_type>() || any.convert<data_type>();
 
@@ -2082,11 +2102,19 @@ bool setter([[maybe_unused]] meta_handle handle, [[maybe_unused]] meta_any &any)
 
 template<typename Type, auto Data>
 inline meta_any getter([[maybe_unused]] meta_handle handle) {
-    if constexpr(std::is_member_object_pointer_v<decltype(Data)>) {
-        static_assert(std::is_invocable_v<decltype(Data), Type>);
+    if constexpr(std::is_function_v<std::remove_pointer_t<decltype(Data)>> || std::is_member_pointer_v<decltype(Data)>) {
+        static_assert(std::is_invocable_v<decltype(Data), Type &>);
         auto *clazz = handle.try_cast<Type>();
-        return clazz ? meta_any{clazz->*Data} : meta_any{};
+
+        if constexpr(std::is_function_v<std::remove_pointer_t<decltype(Data)>>) {
+            return clazz ? meta_any{Data(*clazz)} : meta_any{};
+        } else if constexpr(std::is_member_function_pointer_v<decltype(Data)>) {
+            return clazz ? meta_any{(clazz->*Data)()} : meta_any{};
+        } else /* if constexpr(std::is_member_object_pointer_v<decltype(Data)>) */ {
+            return clazz ? meta_any{clazz->*Data} : meta_any{};
+        }
     } else {
+        static_assert(std::is_pointer_v<decltype(Data)>);
         return meta_any{*Data};
     }
 }

+ 63 - 0
test/entt/meta/meta.cpp

@@ -80,6 +80,16 @@ struct func_type {
     inline static int value = 0;
 };
 
+struct setter_getter_type {
+    int value{};
+
+    int setter(int value) { return this->value = value; }
+    int getter() { return value; }
+
+    static int static_setter(setter_getter_type &type, int value) { return type.value = value; }
+    static int static_getter(const setter_getter_type &type) { return type.value; }
+};
+
 struct not_comparable_type {
     bool operator==(const not_comparable_type &) const = delete;
 };
@@ -146,6 +156,11 @@ struct Meta: public ::testing::Test {
                 .func<&func_type::h>("h", std::make_pair(properties::prop_bool, false))
                 .func<&func_type::k>("k", std::make_pair(properties::prop_bool, false));
 
+        entt::reflect<setter_getter_type>("setter_getter")
+                .data<&setter_getter_type::static_setter, &setter_getter_type::static_getter>("x")
+                .data<&setter_getter_type::setter, &setter_getter_type::getter>("y")
+                .data<&setter_getter_type::static_setter, &setter_getter_type::getter>("z");
+
         entt::reflect<an_abstract_type>("an_abstract_type", std::make_pair(properties::prop_bool, false))
                 .data<&an_abstract_type::i>("i")
                 .func<&an_abstract_type::f>("f")
@@ -884,6 +899,54 @@ TEST_F(Meta, MetaDataSetConvert) {
     ASSERT_EQ(instance.i, 3);
 }
 
+TEST_F(Meta, MetaDataSetterGetterAsFreeFunctions) {
+    auto data = entt::resolve<setter_getter_type>().data("x");
+    setter_getter_type instance{};
+
+    ASSERT_TRUE(data);
+    ASSERT_NE(data, entt::meta_data{});
+    ASSERT_EQ(data.parent(), entt::resolve("setter_getter"));
+    ASSERT_EQ(data.type(), entt::resolve<int>());
+    ASSERT_STREQ(data.name(), "x");
+    ASSERT_FALSE(data.is_const());
+    ASSERT_FALSE(data.is_static());
+    ASSERT_EQ(data.get(instance).cast<int>(), 0);
+    ASSERT_TRUE(data.set(instance, 42));
+    ASSERT_EQ(data.get(instance).cast<int>(), 42);
+}
+
+TEST_F(Meta, MetaDataSetterGetterAsMemberFunctions) {
+    auto data = entt::resolve<setter_getter_type>().data("y");
+    setter_getter_type instance{};
+
+    ASSERT_TRUE(data);
+    ASSERT_NE(data, entt::meta_data{});
+    ASSERT_EQ(data.parent(), entt::resolve("setter_getter"));
+    ASSERT_EQ(data.type(), entt::resolve<int>());
+    ASSERT_STREQ(data.name(), "y");
+    ASSERT_FALSE(data.is_const());
+    ASSERT_FALSE(data.is_static());
+    ASSERT_EQ(data.get(instance).cast<int>(), 0);
+    ASSERT_TRUE(data.set(instance, 42));
+    ASSERT_EQ(data.get(instance).cast<int>(), 42);
+}
+
+TEST_F(Meta, MetaDataSetterGetterMixed) {
+    auto data = entt::resolve<setter_getter_type>().data("z");
+    setter_getter_type instance{};
+
+    ASSERT_TRUE(data);
+    ASSERT_NE(data, entt::meta_data{});
+    ASSERT_EQ(data.parent(), entt::resolve("setter_getter"));
+    ASSERT_EQ(data.type(), entt::resolve<int>());
+    ASSERT_STREQ(data.name(), "z");
+    ASSERT_FALSE(data.is_const());
+    ASSERT_FALSE(data.is_static());
+    ASSERT_EQ(data.get(instance).cast<int>(), 0);
+    ASSERT_TRUE(data.set(instance, 42));
+    ASSERT_EQ(data.get(instance).cast<int>(), 42);
+}
+
 TEST_F(Meta, MetaFunc) {
     auto func = entt::resolve<func_type>().func("f2");
     func_type instance{};