Просмотр исходного кода

meta: built-in support for arrays

Michele Caini 6 лет назад
Родитель
Сommit
b983845745
4 измененных файлов с 250 добавлено и 68 удалено
  1. 3 0
      TODO
  2. 41 28
      src/entt/meta/factory.hpp
  3. 138 40
      src/entt/meta/meta.hpp
  4. 68 0
      test/entt/meta/meta.cpp

+ 3 - 0
TODO

@@ -19,4 +19,7 @@
 * review sparse set to allow customization (mix pack in the spec, base is position only)
   - non-owning groups can iterate pages and skip empty ones, this should mitigate the lack of the packed array
 * review 64 bit id: user defined area + dedicated member on the registry to set it
+* early out in views using bitmasks with bloom filter like access based on modulus
+  - standard each, use bitmask to speed up the whole thing and avoid accessing the pools to test for the page
+  - iterator based each with a couple of iterators passed from outside (use bitmask + has)
 * reactive systems

+ 41 - 28
src/entt/meta/factory.hpp

@@ -39,20 +39,6 @@ template<typename Type>
 class meta_factory {
     static_assert(std::is_object_v<Type> && !(std::is_const_v<Type> || std::is_volatile_v<Type>));
 
-    template<auto Data>
-    static std::enable_if_t<std::is_member_object_pointer_v<decltype(Data)>, decltype(std::declval<Type>().*Data)>
-    actual_type();
-
-    template<auto Data>
-    static std::enable_if_t<std::is_pointer_v<decltype(Data)>, decltype(*Data)>
-    actual_type();
-
-    template<auto Data>
-    using data_type = std::remove_reference_t<decltype(meta_factory::actual_type<Data>())>;
-
-    template<auto Func>
-    using func_type = internal::meta_function_helper<std::integral_constant<decltype(Func), Func>>;
-
     template<typename Node>
     inline bool duplicate(const hashed_string &name, const Node *node) ENTT_NOEXCEPT {
         return node ? node->name == name || duplicate(name, node->next) : false;
@@ -69,7 +55,7 @@ class meta_factory {
 
     template<typename Owner, typename Property, typename... Other>
     internal::meta_prop_node * properties(Property &&property, Other &&... other) {
-        static std::decay_t<Property> prop{};
+        static std::remove_cv_t<std::remove_reference_t<Property>> prop{};
 
         static internal::meta_prop_node node{
             nullptr,
@@ -99,6 +85,7 @@ class meta_factory {
             std::is_void_v<Type>,
             std::is_integral_v<Type>,
             std::is_floating_point_v<Type>,
+            std::is_array_v<Type>,
             std::is_enum_v<Type>,
             std::is_union_v<Type>,
             std::is_class_v<Type>,
@@ -106,6 +93,7 @@ class meta_factory {
             std::is_function_v<Type>,
             std::is_member_object_pointer_v<Type>,
             std::is_member_function_pointer_v<Type>,
+            std::extent_v<Type>,
             []() -> meta_type {
                 return internal::meta_info<std::remove_pointer_t<Type>>::resolve();
             },
@@ -248,7 +236,7 @@ public:
      */
     template<typename To>
     meta_factory conv() ENTT_NOEXCEPT {
-        static_assert(std::is_convertible_v<Type, std::decay_t<To>>);
+        static_assert(std::is_convertible_v<Type, To>);
         auto * const type = internal::meta_info<Type>::resolve();
 
         static internal::meta_conv_node node{
@@ -257,7 +245,7 @@ public:
             nullptr,
             &internal::meta_info<To>::resolve,
             [](void *instance) -> meta_any {
-                return static_cast<std::decay_t<To>>(*static_cast<Type *>(instance));
+                return static_cast<To>(*static_cast<Type *>(instance));
             },
             []() -> meta_conv {
                 return &node;
@@ -428,8 +416,8 @@ public:
                 true,
                 true,
                 &internal::meta_info<Type>::resolve,
-                [](meta_handle, meta_any &) { return false; },
-                [](meta_handle) -> meta_any { return Data; },
+                [](meta_handle, meta_any, meta_any) { return false; },
+                [](meta_handle, meta_any) -> meta_any { return Data; },
                 []() -> meta_data {
                     return &node;
                 }
@@ -437,17 +425,41 @@ public:
 
             node.prop = properties<std::integral_constant<Type, Data>>(std::forward<Property>(property)...);
             curr = &node;
+        } else if constexpr(std::is_member_object_pointer_v<decltype(Data)>) {
+            using data_type = std::remove_reference_t<decltype(std::declval<Type>().*Data)>;
+
+            static internal::meta_data_node node{
+                &internal::meta_info<Type>::template data<Data>,
+                {},
+                type,
+                nullptr,
+                nullptr,
+                std::is_const_v<data_type>,
+                !std::is_member_object_pointer_v<decltype(Data)>,
+                &internal::meta_info<data_type>::resolve,
+                &internal::setter<std::is_const_v<data_type>, Type, Data>,
+                &internal::getter<Type, Data>,
+                []() -> meta_data {
+                    return &node;
+                }
+            };
+
+            node.prop = properties<std::integral_constant<decltype(Data), Data>>(std::forward<Property>(property)...);
+            curr = &node;
         } else {
+            static_assert(std::is_pointer_v<decltype(Data)>);
+            using data_type = std::remove_pointer_t<decltype(Data)>;
+
             static internal::meta_data_node node{
                 &internal::meta_info<Type>::template data<Data>,
                 {},
                 type,
                 nullptr,
                 nullptr,
-                std::is_const_v<data_type<Data>>,
+                std::is_const_v<data_type>,
                 !std::is_member_object_pointer_v<decltype(Data)>,
-                &internal::meta_info<data_type<Data>>::resolve,
-                &internal::setter<std::is_const_v<data_type<Data>>, Type, Data>,
+                &internal::meta_info<data_type>::resolve,
+                &internal::setter<std::is_const_v<data_type>, Type, Data>,
                 &internal::getter<Type, Data>,
                 []() -> meta_data {
                     return &node;
@@ -540,6 +552,7 @@ public:
     template<auto Func, typename... Property>
     meta_factory func(const char *str, Property &&... property) ENTT_NOEXCEPT {
         using owner_type = std::integral_constant<decltype(Func), Func>;
+        using func_type = internal::meta_function_helper<std::integral_constant<decltype(Func), Func>>;
         auto * const type = internal::meta_info<Type>::resolve();
 
         static internal::meta_func_node node{
@@ -548,13 +561,13 @@ public:
             type,
             nullptr,
             nullptr,
-            func_type<Func>::size,
-            func_type<Func>::is_const,
-            func_type<Func>::is_static,
-            &internal::meta_info<typename func_type<Func>::return_type>::resolve,
-            &func_type<Func>::arg,
+            func_type::size,
+            func_type::is_const,
+            func_type::is_static,
+            &internal::meta_info<typename func_type::return_type>::resolve,
+            &func_type::arg,
             [](meta_handle handle, meta_any *any) {
-                return internal::invoke<Type, Func>(handle, any, std::make_index_sequence<func_type<Func>::size>{});
+                return internal::invoke<Type, Func>(handle, any, std::make_index_sequence<func_type::size>{});
             },
             []() -> meta_func {
                 return &node;

+ 138 - 40
src/entt/meta/meta.hpp

@@ -99,8 +99,8 @@ struct meta_data_node {
     const bool is_const;
     const bool is_static;
     meta_type_node *(* const type)();
-    bool(* const set)(meta_handle, meta_any &);
-    meta_any(* const get)(meta_handle);
+    bool(* const set)(meta_handle, meta_any, meta_any);
+    meta_any(* const get)(meta_handle, meta_any);
     meta_data(* const meta)();
 };
 
@@ -123,12 +123,14 @@ struct meta_func_node {
 
 
 struct meta_type_node {
+    using size_type = std::size_t;
     hashed_string name;
     meta_type_node * next;
     meta_prop_node * prop;
     const bool is_void;
     const bool is_integral;
     const bool is_floating_point;
+    const bool is_array;
     const bool is_enum;
     const bool is_union;
     const bool is_class;
@@ -136,6 +138,7 @@ struct meta_type_node {
     const bool is_function;
     const bool is_member_object_pointer;
     const bool is_member_function_pointer;
+    const size_type extent;
     meta_type(* const remove_pointer)();
     bool(* const destroy)(meta_handle);
     meta_type(* const meta)();
@@ -377,9 +380,9 @@ public:
      * @tparam Type Type of object to use to initialize the container.
      * @param type An instance of an object to use to initialize the container.
      */
-    template<typename Type, typename = std::enable_if_t<!std::is_same_v<std::decay_t<Type>, meta_any>>>
+    template<typename Type, typename = std::enable_if_t<!std::is_same_v<std::remove_cv_t<std::remove_reference_t<Type>>, meta_any>>>
     meta_any(Type &&type) {
-        using actual_type = std::decay_t<Type>;
+        using actual_type = std::remove_cv_t<std::remove_reference_t<Type>>;
         node = internal::meta_info<Type>::resolve();
 
         compare_fn = &compare<actual_type>;
@@ -656,7 +659,7 @@ public:
      * @tparam Type Type of object to use to initialize the handle.
      * @param obj A reference to an object to use to initialize the handle.
      */
-    template<typename Type, typename = std::enable_if_t<!std::is_same_v<std::decay_t<Type>, meta_handle>>>
+    template<typename Type, typename = std::enable_if_t<!std::is_same_v<std::remove_cv_t<std::remove_reference_t<Type>>, meta_handle>>>
     meta_handle(Type &&obj) ENTT_NOEXCEPT
         : meta_handle{0, std::forward<Type>(obj)}
     {}
@@ -1230,22 +1233,56 @@ public:
      */
     template<typename Type>
     inline bool set(meta_handle handle, Type &&value) const {
-        meta_any any{std::forward<Type>(value)};
-        return node->set(handle, any);
+        return node->set(handle, meta_any{}, std::forward<Type>(value));
+    }
+
+    /**
+     * @brief Sets the i-th element of an array enclosed by a given meta type.
+     *
+     * It must be possible to cast the instance to the parent type of the meta
+     * data. Otherwise, invoking the setter results in an undefined
+     * behavior.<br/>
+     * The type of the value must coincide exactly with that of the array type
+     * enclosed by the meta data. Otherwise, invoking the setter does nothing.
+     *
+     * @tparam Type Type of value to assign.
+     * @param handle An opaque pointer to an instance of the underlying type.
+     * @param index Position of the underlying element to set.
+     * @param value Parameter to use to set the underlying element.
+     * @return True in case of success, false otherwise.
+     */
+    template<typename Type>
+    inline bool set(meta_handle handle, std::size_t index, Type &&value) const {
+        assert(index < node->type()->extent);
+        return node->set(handle, index, std::forward<Type>(value));
     }
 
     /**
      * @brief Gets the value of the variable enclosed by a given meta type.
      *
      * It must be possible to cast the instance to the parent type of the meta
-     * function. Otherwise, invoking the getter results in an undefined
-     * behavior.
+     * data. Otherwise, invoking the getter results in an undefined behavior.
      *
      * @param handle An opaque pointer to an instance of the underlying type.
      * @return A meta any containing the value of the underlying variable.
      */
     inline meta_any get(meta_handle handle) const ENTT_NOEXCEPT {
-        return node->get(handle);
+        return node->get(handle, meta_any{});
+    }
+
+    /**
+     * @brief Gets the i-th element of an array enclosed by a given meta type.
+     *
+     * It must be possible to cast the instance to the parent type of the meta
+     * data. Otherwise, invoking the getter results in an undefined behavior.
+     *
+     * @param handle An opaque pointer to an instance of the underlying type.
+     * @param index Position of the underlying element to get.
+     * @return A meta any containing the value of the underlying element.
+     */
+    inline meta_any get(meta_handle handle, std::size_t index) const ENTT_NOEXCEPT {
+        assert(index < node->type()->extent);
+        return node->get(handle, index);
     }
 
     /**
@@ -1497,6 +1534,9 @@ class meta_type {
     {}
 
 public:
+    /*! @brief Unsigned integer type. */
+    using size_type = typename internal::meta_type_node::size_type;
+
     /*! @brief Default constructor. */
     inline meta_type() ENTT_NOEXCEPT
         : node{nullptr}
@@ -1537,6 +1577,15 @@ public:
         return node->is_floating_point;
     }
 
+    /**
+     * @brief Indicates whether a given meta type refers to an array type or
+     * not.
+     * @return True if the underlying type is an array type, false otherwise.
+     */
+    inline bool is_array() const ENTT_NOEXCEPT {
+        return node->is_array;
+    }
+
     /**
      * @brief Indicates whether a given meta type refers to an enum or not.
      * @return True if the underlying type is an enum, false otherwise.
@@ -1598,6 +1647,16 @@ public:
         return node->is_member_function_pointer;
     }
 
+    /**
+     * @brief If a given meta type refers to an array type, provides the number
+     * of elements of the array.
+     * @return The number of elements of the array if the underlying type is an
+     * array type, 0 otherwise.
+     */
+    inline size_type extent() const ENTT_NOEXCEPT {
+        return node->extent;
+    }
+
     /**
      * @brief Provides the meta type for which the pointer is defined.
      * @return The meta type for which the pointer is defined or this meta type
@@ -2010,25 +2069,29 @@ struct meta_function_helper<std::integral_constant<decltype(Func), Func>>: declt
 
 template<typename Type>
 inline bool destroy([[maybe_unused]] meta_handle handle) {
-    if constexpr(std::is_object_v<Type>) {
-        return handle.type() == meta_info<Type>::resolve()->meta()
-                ? (static_cast<Type *>(handle.data())->~Type(), true)
-                : false;
-    } else {
-        return false;
+    bool accepted = false;
+
+    if constexpr(std::is_object_v<Type> && !std::is_array_v<Type>) {
+        accepted = (handle.type() == meta_info<Type>::resolve()->meta());
+
+        if(accepted) {
+            static_cast<Type *>(handle.data())->~Type();
+        }
     }
+
+    return accepted;
 }
 
 
 template<typename Type, typename... Args, std::size_t... Indexes>
 inline meta_any construct(meta_any * const args, std::index_sequence<Indexes...>) {
-    [[maybe_unused]] std::array<bool, sizeof...(Args)> can_cast{{(args+Indexes)->can_cast<std::decay_t<Args>>()...}};
-    [[maybe_unused]] std::array<bool, sizeof...(Args)> can_convert{{(std::get<Indexes>(can_cast) ? false : (args+Indexes)->can_convert<std::decay_t<Args>>())...}};
+    [[maybe_unused]] std::array<bool, sizeof...(Args)> can_cast{{(args+Indexes)->can_cast<std::remove_cv_t<std::remove_reference_t<Args>>>()...}};
+    [[maybe_unused]] std::array<bool, sizeof...(Args)> can_convert{{(std::get<Indexes>(can_cast) ? false : (args+Indexes)->can_convert<std::remove_cv_t<std::remove_reference_t<Args>>>())...}};
     meta_any any{};
 
     if(((std::get<Indexes>(can_cast) || std::get<Indexes>(can_convert)) && ...)) {
-        ((std::get<Indexes>(can_convert) ? void((args+Indexes)->convert<std::decay_t<Args>>()) : void()), ...);
-        any = Type{(args+Indexes)->cast<std::decay_t<Args>>()...};
+        ((std::get<Indexes>(can_convert) ? void((args+Indexes)->convert<std::remove_cv_t<std::remove_reference_t<Args>>>()) : void()), ...);
+        any = Type{(args+Indexes)->cast<std::remove_cv_t<std::remove_reference_t<Args>>>()...};
     }
 
     return any;
@@ -2036,55 +2099,88 @@ inline meta_any construct(meta_any * const args, std::index_sequence<Indexes...>
 
 
 template<bool Const, typename Type, auto Data>
-bool setter([[maybe_unused]] meta_handle handle, [[maybe_unused]] meta_any &any) {
+bool setter([[maybe_unused]] meta_handle handle, [[maybe_unused]] meta_any index, [[maybe_unused]] meta_any value) {
     bool accepted = false;
 
-    if constexpr(Const) {
-        return accepted;
-    } else {
+    if constexpr(!Const) {
         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::decay_t<std::tuple_element_t<!std::is_member_function_pointer_v<decltype(Data)>, typename helper_type::args_type>>;
             static_assert(std::is_invocable_v<decltype(Data), Type *, data_type>);
-            accepted = any.can_cast<data_type>() || any.convert<data_type>();
+            accepted = value.can_cast<data_type>() || value.convert<data_type>();
             auto *clazz = handle.try_cast<Type>();
 
             if(accepted && clazz) {
-                std::invoke(Data, clazz, any.cast<data_type>());
+                std::invoke(Data, clazz, value.cast<data_type>());
             }
         } else if constexpr(std::is_member_object_pointer_v<decltype(Data)>) {
-            using data_type = std::decay_t<decltype(std::declval<Type>().*Data)>;
+            using data_type = std::remove_cv_t<std::remove_reference_t<decltype(std::declval<Type>().*Data)>>;
             static_assert(std::is_invocable_v<decltype(Data), Type>);
-            accepted = any.can_cast<data_type>() || any.convert<data_type>();
             auto *clazz = handle.try_cast<Type>();
 
-            if(accepted && clazz) {
-                std::invoke(Data, clazz) = any.cast<data_type>();
+            if constexpr(std::is_array_v<data_type>) {
+                using underlying_type = std::remove_extent_t<data_type>;
+                accepted = index.can_cast<std::size_t>() && (value.can_cast<underlying_type>() || value.convert<underlying_type>());
+
+                if(accepted && clazz) {
+                    std::invoke(Data, clazz)[index.cast<std::size_t>()] = value.cast<underlying_type>();
+                }
+            } else {
+                accepted = value.can_cast<data_type>() || value.convert<data_type>();
+
+                if(accepted && clazz) {
+                    std::invoke(Data, clazz) = value.cast<data_type>();
+                }
             }
         } else {
             static_assert(std::is_pointer_v<decltype(Data)>);
-            using data_type = std::decay_t<decltype(*Data)>;
-            accepted = any.can_cast<data_type>() || any.convert<data_type>();
+            using data_type = std::remove_cv_t<std::remove_reference_t<decltype(*Data)>>;
+
+            if constexpr(std::is_array_v<data_type>) {
+                using underlying_type = std::remove_extent_t<data_type>;
+                accepted = index.can_cast<std::size_t>() && (value.can_cast<underlying_type>() || value.convert<underlying_type>());
 
-            if(accepted) {
-                *Data = any.cast<data_type>();
+                if(accepted) {
+                    (*Data)[index.cast<std::size_t>()] = value.cast<underlying_type>();
+                }
+            } else {
+                accepted = value.can_cast<data_type>() || value.convert<data_type>();
+
+                if(accepted) {
+                    *Data = value.cast<data_type>();
+                }
             }
         }
-
-        return accepted;
     }
+
+    return accepted;
 }
 
 
 template<typename Type, auto Data>
-inline meta_any getter([[maybe_unused]] meta_handle handle) {
-    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 *>);
+inline meta_any getter([[maybe_unused]] meta_handle handle, [[maybe_unused]] meta_any index) {
+    if constexpr(std::is_function_v<std::remove_pointer_t<decltype(Data)>> || std::is_member_function_pointer_v<decltype(Data)>) {
+       static_assert(std::is_invocable_v<decltype(Data), Type *>);
         auto *clazz = handle.try_cast<Type>();
         return clazz ? std::invoke(Data, clazz) : meta_any{};
+    } else if constexpr(std::is_member_object_pointer_v<decltype(Data)>) {
+        using data_type = std::remove_cv_t<std::remove_reference_t<decltype(std::declval<Type>().*Data)>>;
+        static_assert(std::is_invocable_v<decltype(Data), Type *>);
+        auto *clazz = handle.try_cast<Type>();
+
+        if constexpr(std::is_array_v<data_type>) {
+            return (clazz && index.can_cast<std::size_t>()) ? std::invoke(Data, clazz)[index.cast<std::size_t>()] : meta_any{};
+        } else {
+            return clazz ? std::invoke(Data, clazz) : meta_any{};
+        }
     } else {
         static_assert(std::is_pointer_v<decltype(Data)>);
-        return meta_any{*Data};
+
+        if constexpr(std::is_array_v<std::remove_pointer_t<decltype(Data)>>) {
+            return index.can_cast<std::size_t>() ? (*Data)[index.cast<std::size_t>()] : meta_any{};
+        } else {
+            return *Data;
+        }
     }
 }
 
@@ -2141,6 +2237,7 @@ meta_type_node * meta_node<Type>::resolve() ENTT_NOEXCEPT {
             std::is_void_v<Type>,
             std::is_integral_v<Type>,
             std::is_floating_point_v<Type>,
+            std::is_array_v<Type>,
             std::is_enum_v<Type>,
             std::is_union_v<Type>,
             std::is_class_v<Type>,
@@ -2148,6 +2245,7 @@ meta_type_node * meta_node<Type>::resolve() ENTT_NOEXCEPT {
             std::is_function_v<Type>,
             std::is_member_object_pointer_v<Type>,
             std::is_member_function_pointer_v<Type>,
+            std::extent_v<Type>,
             []() -> meta_type {
                 return internal::meta_info<std::remove_pointer_t<Type>>::resolve();
             },

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

@@ -71,6 +71,11 @@ struct data_type {
     empty_type empty{};
 };
 
+struct array_type {
+    static inline int global[3];
+    int local[3];
+};
+
 struct func_type {
     int f(const base_type &, int a, int b) { return f(a, b); }
     int f(int a, int b) { value = a; return b*b; }
@@ -154,6 +159,10 @@ struct Meta: public ::testing::Test {
                 .data<&data_type::k>("k", std::make_pair(properties::prop_int, 3))
                 .data<&data_type::empty>("empty");
 
+        entt::reflect<array_type>("array")
+                .data<&array_type::global>("global")
+                .data<&array_type::local>("local");
+
         entt::reflect<func_type>("func")
                 .func<entt::overload<int(const base_type &, int, int)>(&func_type::f)>("f3")
                 .func<entt::overload<int(int, int)>(&func_type::f)>("f2", std::make_pair(properties::prop_bool, false))
@@ -1010,6 +1019,65 @@ TEST_F(Meta, MetaDataSetterGetterMixed) {
     ASSERT_EQ(data.get(instance).cast<int>(), 42);
 }
 
+TEST_F(Meta, MetaDataArrayStatic) {
+    auto data = entt::resolve<array_type>().data("global");
+
+    array_type::global[0] = 3;
+    array_type::global[1] = 5;
+    array_type::global[2] = 7;
+
+    ASSERT_TRUE(data);
+    ASSERT_NE(data, entt::meta_data{});
+    ASSERT_EQ(data.parent(), entt::resolve("array"));
+    ASSERT_EQ(data.type(), entt::resolve<int[3]>());
+    ASSERT_STREQ(data.name(), "global");
+    ASSERT_FALSE(data.is_const());
+    ASSERT_TRUE(data.is_static());
+    ASSERT_TRUE(data.type().is_array());
+    ASSERT_EQ(data.type().extent(), 3);
+    ASSERT_EQ(data.get(nullptr, 0).cast<int>(), 3);
+    ASSERT_EQ(data.get(nullptr, 1).cast<int>(), 5);
+    ASSERT_EQ(data.get(nullptr, 2).cast<int>(), 7);
+    ASSERT_FALSE(data.set(nullptr, 0, 'c'));
+    ASSERT_EQ(data.get(nullptr, 0).cast<int>(), 3);
+    ASSERT_TRUE(data.set(nullptr, 0, data.get(nullptr, 0).cast<int>()+2));
+    ASSERT_TRUE(data.set(nullptr, 1, data.get(nullptr, 1).cast<int>()+2));
+    ASSERT_TRUE(data.set(nullptr, 2, data.get(nullptr, 2).cast<int>()+2));
+    ASSERT_EQ(data.get(nullptr, 0).cast<int>(), 5);
+    ASSERT_EQ(data.get(nullptr, 1).cast<int>(), 7);
+    ASSERT_EQ(data.get(nullptr, 2).cast<int>(), 9);
+}
+
+TEST_F(Meta, MetaDataArray) {
+    auto data = entt::resolve<array_type>().data("local");
+    array_type instance;
+
+    instance.local[0] = 3;
+    instance.local[1] = 5;
+    instance.local[2] = 7;
+
+    ASSERT_TRUE(data);
+    ASSERT_NE(data, entt::meta_data{});
+    ASSERT_EQ(data.parent(), entt::resolve("array"));
+    ASSERT_EQ(data.type(), entt::resolve<int[3]>());
+    ASSERT_STREQ(data.name(), "local");
+    ASSERT_FALSE(data.is_const());
+    ASSERT_FALSE(data.is_static());
+    ASSERT_TRUE(data.type().is_array());
+    ASSERT_EQ(data.type().extent(), 3);
+    ASSERT_EQ(data.get(instance, 0).cast<int>(), 3);
+    ASSERT_EQ(data.get(instance, 1).cast<int>(), 5);
+    ASSERT_EQ(data.get(instance, 2).cast<int>(), 7);
+    ASSERT_FALSE(data.set(instance, 0, 'c'));
+    ASSERT_EQ(data.get(instance, 0).cast<int>(), 3);
+    ASSERT_TRUE(data.set(instance, 0, data.get(instance, 0).cast<int>()+2));
+    ASSERT_TRUE(data.set(instance, 1, data.get(instance, 1).cast<int>()+2));
+    ASSERT_TRUE(data.set(instance, 2, data.get(instance, 2).cast<int>()+2));
+    ASSERT_EQ(data.get(instance, 0).cast<int>(), 5);
+    ASSERT_EQ(data.get(instance, 1).cast<int>(), 7);
+    ASSERT_EQ(data.get(instance, 2).cast<int>(), 9);
+}
+
 TEST_F(Meta, MetaFunc) {
     auto func = entt::resolve<func_type>().func("f2");
     func_type instance{};