diff --git a/core/object/method_info.h b/core/object/method_info.h index d2b2668d1c61..229e215dfb36 100644 --- a/core/object/method_info.h +++ b/core/object/method_info.h @@ -48,16 +48,15 @@ enum MethodFlags { struct MethodInfo { STRUCT_DECLARE(MethodInfo); - STRUCT_MEMBER_PRIMITIVE(String, name, String()); - STRUCT_MEMBER_PRIMITIVE_FROM_TO_ALIAS(List, arguments, "args", List()); - STRUCT_MEMBER_PRIMITIVE_ALIAS(Vector, default_arguments, "default_args", Vector()); - STRUCT_MEMBER_PRIMITIVE(uint32_t, flags, METHOD_FLAGS_DEFAULT); - STRUCT_MEMBER_PRIMITIVE(int, id, 0); + STRUCT_MEMBER(String, name, String()); + STRUCT_MEMBER_FROM_TO_ALIAS(List, arguments, "args", List()); + STRUCT_MEMBER_ALIAS(Vector, default_arguments, "default_args", Vector()); + STRUCT_MEMBER(uint32_t, flags, METHOD_FLAGS_DEFAULT); + STRUCT_MEMBER(int, id, 0); STRUCT_MEMBER_STRUCT_FROM_TO_ALIAS(PropertyInfo, return_val, "return", PropertyInfo()); - - STRUCT_MEMBER_PRIMITIVE(int, return_val_metadata, 0); - STRUCT_MEMBER_PRIMITIVE(Vector, arguments_metadata, Vector()); - STRUCT_LAYOUT_OWNER(Object, MethodInfo, struct name, struct arguments, struct default_arguments, struct flags, struct id, struct return_val, struct return_val_metadata, struct arguments_metadata); + STRUCT_MEMBER(int, return_val_metadata, 0); + STRUCT_MEMBER(Vector, arguments_metadata, Vector()); + STRUCT_LAYOUT(Object, MethodInfo, struct name, struct arguments, struct default_arguments, struct flags, struct id, struct return_val, struct return_val_metadata, struct arguments_metadata); int get_argument_meta(int p_arg) const { ERR_FAIL_COND_V(p_arg < -1 || p_arg > arguments.size(), 0); diff --git a/core/object/object.h b/core/object/object.h index 6386d1299726..9ae98bb7a896 100644 --- a/core/object/object.h +++ b/core/object/object.h @@ -344,10 +344,10 @@ class Object { struct Connection { STRUCT_DECLARE(Connection); - STRUCT_MEMBER_PRIMITIVE(::Signal, signal, ::Signal()); - STRUCT_MEMBER_PRIMITIVE(Callable, callable, Callable()); - STRUCT_MEMBER_PRIMITIVE(uint32_t, flags, 0); - STRUCT_LAYOUT_OWNER(Object, Connection, struct signal, struct callable, struct flags); + STRUCT_MEMBER(::Signal, signal, ::Signal()); + STRUCT_MEMBER(Callable, callable, Callable()); + STRUCT_MEMBER(uint32_t, flags, 0); + STRUCT_LAYOUT(Object, Connection, struct signal, struct callable, struct flags); bool operator<(const Connection &p_conn) const; diff --git a/core/object/property_info.h b/core/object/property_info.h index a6c014673fdb..332e7ed98f90 100644 --- a/core/object/property_info.h +++ b/core/object/property_info.h @@ -117,13 +117,13 @@ enum PropertyUsageFlags { struct PropertyInfo { STRUCT_DECLARE(PropertyInfo); - STRUCT_MEMBER_PRIMITIVE(String, name, String()); - STRUCT_MEMBER_PRIMITIVE(StringName, class_name, StringName()); - STRUCT_MEMBER_PRIMITIVE_FROM(Variant::Type, type, Variant::NIL); - STRUCT_MEMBER_PRIMITIVE_FROM(PropertyHint, hint, PROPERTY_HINT_NONE); - STRUCT_MEMBER_PRIMITIVE(String, hint_string, String()); - STRUCT_MEMBER_PRIMITIVE(uint32_t, usage, PROPERTY_USAGE_DEFAULT); - STRUCT_LAYOUT_OWNER(Object, PropertyInfo, struct name, struct class_name, struct type, struct hint, struct hint_string, struct usage); + STRUCT_MEMBER(String, name, String()); + STRUCT_MEMBER(StringName, class_name, StringName()); + STRUCT_MEMBER_FROM(Variant::Type, type, Variant::NIL); + STRUCT_MEMBER_FROM(PropertyHint, hint, PROPERTY_HINT_NONE); + STRUCT_MEMBER(String, hint_string, String()); + STRUCT_MEMBER(uint32_t, usage, PROPERTY_USAGE_DEFAULT); + STRUCT_LAYOUT(Object, PropertyInfo, struct name, struct class_name, struct type, struct hint, struct hint_string, struct usage); _FORCE_INLINE_ PropertyInfo added_usage(uint32_t p_fl) const { PropertyInfo pi = *this; diff --git a/core/variant/struct_generator.h b/core/variant/struct_generator.h index 710ebc5f8ec6..8816de97338c 100644 --- a/core/variant/struct_generator.h +++ b/core/variant/struct_generator.h @@ -39,8 +39,16 @@ class TypedArray; template class Struct; +/* The following set of macros let you seamlessly add reflection data to + * a C++ struct or class so that it can be exposed as a Godot Struct. + * The StructInfo struct below uses these macros and serves as a good + * example of how these macros should be used.*/ + +// Goes at the top of every exposed C++ struct. #define STRUCT_DECLARE(m_struct_name) using StructType = m_struct_name +// Creates the typedef for a non-pointer struct member, along with some helper functions. +// "Alias" means that the exposed name can differ from the internal name. #define STRUCT_MEMBER_TYPEDEF_ALIAS(m_type, m_member_name, m_member_name_alias, m_default) \ using Type = m_type; \ _FORCE_INLINE_ static const StringName get_name() { return SNAME(m_member_name_alias); } \ @@ -49,22 +57,31 @@ class Struct; _FORCE_INLINE_ static Type get(const StructType &p_struct) { return p_struct.m_member_name; } \ _FORCE_INLINE_ static Type get_default_value() { return m_default; } \ _FORCE_INLINE_ static void set_variant(StructType &p_struct, const Variant &p_variant) { p_struct.m_member_name = from_variant(p_variant); } \ - _FORCE_INLINE_ static void set(StructType &p_struct, const m_type &p_value) { p_struct.m_member_name = p_value; } + _FORCE_INLINE_ static void set(StructType &p_struct, const Type &p_value) { p_struct.m_member_name = p_value; } +// Shorter macro you can use when the exposed name is the same as the internal name. #define STRUCT_MEMBER_TYPEDEF(m_type, m_member_name, m_default) \ STRUCT_MEMBER_TYPEDEF_ALIAS(m_type, m_member_name, #m_member_name, m_default) -#define STRUCT_MEMBER_TYPEDEF_POINTER(m_type, m_member_name, m_default) \ +// Same as above, but for pointer members. +#define STRUCT_MEMBER_TYPEDEF_POINTER_ALIAS(m_type, m_member_name, m_member_name_alias, m_default) \ using Type = m_type *; \ - _FORCE_INLINE_ static const StringName get_name() { return SNAME(#m_member_name); } \ + _FORCE_INLINE_ static const StringName get_name() { return SNAME(m_member_name_alias); } \ _FORCE_INLINE_ static Variant get_variant(const StructType &p_struct) { return to_variant(p_struct.m_member_name); } \ _FORCE_INLINE_ static const Variant get_default_value_variant() { return to_variant(m_default); } \ _FORCE_INLINE_ static Type get(const StructType &p_struct) { return p_struct.m_member_name; } \ - _FORCE_INLINE_ static const m_type *get_default_value() { return m_default; } \ + _FORCE_INLINE_ static const Type get_default_value() { return m_default; } \ _FORCE_INLINE_ static void set_variant(StructType &p_struct, const Variant &p_variant) { p_struct.m_member_name = from_variant(p_variant); } \ _FORCE_INLINE_ static void set(StructType &p_struct, Type p_value) { p_struct.m_member_name = p_value; } -#define STRUCT_MEMBER_PRIMITIVE_ALIAS(m_type, m_member_name, m_member_name_alias, m_default) \ +// Creates the typedef for a pointer struct member, along with some helper functions. +#define STRUCT_MEMBER_TYPEDEF_POINTER(m_type, m_member_name, m_default) \ + STRUCT_MEMBER_TYPEDEF_POINTER_ALIAS(m_type, m_member_name, #m_member_name, m_default) + +// Creates all the reflection data for a struct member and stores it as static properties of a struct with +// the same name as the member. This particular macro only works for primitive Variant types. For more +// complicated types, such as class values, class pointers, or structs, use the corresponding macro below. +#define STRUCT_MEMBER_ALIAS(m_type, m_member_name, m_member_name_alias, m_default) \ m_type m_member_name = m_default; \ struct m_member_name { \ _FORCE_INLINE_ static m_type from_variant(const Variant &p_variant) { return p_variant; } \ @@ -75,7 +92,12 @@ class Struct; static const StructInfo *get_struct_member_info() { return nullptr; } \ } -#define STRUCT_MEMBER_PRIMITIVE_FROM(m_type, m_member_name, m_default) \ +#define STRUCT_MEMBER(m_type, m_member_name, m_default) \ + STRUCT_MEMBER_ALIAS(m_type, m_member_name, #m_member_name, m_default) + +// Macros that include _FROM allow you to customize the way the struct is converted from a Variant. You must +// implement the from_variant(const Variant &p_variant) function in the corresponding .cpp file. +#define STRUCT_MEMBER_FROM(m_type, m_member_name, m_default) \ m_type m_member_name = m_default; \ struct m_member_name { \ static m_type from_variant(const Variant &p_variant); \ @@ -86,10 +108,9 @@ class Struct; static const StructInfo *get_struct_member_info() { return nullptr; } \ } -#define STRUCT_MEMBER_PRIMITIVE(m_type, m_member_name, m_default) \ - STRUCT_MEMBER_PRIMITIVE_ALIAS(m_type, m_member_name, #m_member_name, m_default) - -#define STRUCT_MEMBER_PRIMITIVE_FROM_TO_ALIAS(m_type, m_member_name, m_member_name_alias, m_default) \ +// Macros that include _TO allow you to customize the way the struct is converted to a Variant. You must +// implement the Variant to_variant function in the corresponding .cpp file. +#define STRUCT_MEMBER_FROM_TO_ALIAS(m_type, m_member_name, m_member_name_alias, m_default) \ m_type m_member_name = m_default; \ struct m_member_name { \ static m_type from_variant(const Variant &p_variant); \ @@ -100,8 +121,8 @@ class Struct; static const StructInfo *get_struct_member_info() { return nullptr; } \ } -#define STRUCT_MEMBER_PRIMITIVE_FROM_TO(m_type, m_member_name, m_default) \ - STRUCT_MEMBER_PRIMITIVE_FROM_TO_ALIAS(m_type, m_member_name, #m_member_name, m_default) +#define STRUCT_MEMBER_FROM_TO(m_type, m_member_name, m_default) \ + STRUCT_MEMBER_FROM_TO_ALIAS(m_type, m_member_name, #m_member_name, m_default) #define STRUCT_MEMBER_CLASS_POINTER(m_type, m_member_name, m_default) \ m_type *m_member_name = m_default; \ @@ -152,25 +173,30 @@ class Struct; #define STRUCT_MEMBER_STRUCT_FROM_TO(m_type, m_member_name, m_default) \ STRUCT_MEMBER_STRUCT_FROM_TO_ALIAS(m_type, m_member_name, #m_member_name, m_default) -#define STRUCT_LAYOUT_WITH_NAME(m_struct_name, m_struct, ...) \ - static const StringName get_struct_name() { \ - return SNAME(m_struct_name); \ - } \ - static const StructInfo &get_struct_info() { \ - return Layout::get_struct_info(); \ - } \ - using Layout = StructLayout; \ - m_struct(const Dictionary &p_dict) { \ - Layout::fill_struct(p_dict, *this); \ - } \ - m_struct(const Array &p_array) { \ - Layout::fill_struct(p_array, *this); \ +// Use after all the struct members have been declared to specialize the StructLayout Template +// and define some helper functions. +#define STRUCT_LAYOUT_ALIAS(m_struct_name, m_struct, ...) \ + static const StringName get_struct_name() { \ + return SNAME(m_struct_name); \ + } \ + static const StructInfo &get_struct_info() { \ + return Layout::get_struct_info(); \ + } \ + using Layout = StructLayout; \ + m_struct(const Dictionary &p_dict) { \ + Layout::fill_struct(p_dict, *this); \ + } \ + m_struct(const Array &p_array) { \ + Layout::fill_struct(p_array, *this); \ } -#define STRUCT_LAYOUT_OWNER(m_owner, m_struct, ...) STRUCT_LAYOUT_WITH_NAME(#m_owner "." #m_struct, m_struct, __VA_ARGS__) - -#define STRUCT_LAYOUT(m_struct, ...) STRUCT_LAYOUT_WITH_NAME(#m_struct, m_struct, __VA_ARGS__) +// Most of the time, the exposed name of a struct follows the pattern "OwningClass.StructName". +#define STRUCT_LAYOUT(m_owner, m_struct, ...) STRUCT_LAYOUT_ALIAS(#m_owner "." #m_struct, m_struct, __VA_ARGS__) +/* The StructLayout template manages all the reflection data for a native struct. It automatically generates + * functions for converting the C++ struct to and from a Godot Struct, Array, or Dictionary. + * The StructType argument is expected to be a struct declared with STRUCT_DECLARE and the StructMember + * arguments are expected to be the reflection structs created by any of the various STRUCT_MEMBER macros. */ template struct StructLayout { static constexpr int32_t struct_member_count = sizeof...(StructMembers); @@ -225,6 +251,9 @@ struct StructLayout { return sizeof...(StructMembers) - TypeFinder::remaining_count - 1; } + // Provides random access member lookup for native Godot Structs. + // It uses recursive types to force the compiler to perform a linear member search + // so that member access is O(1) at runtime. private: template struct TypeFinder; @@ -245,15 +274,18 @@ struct StructLayout { }; }; +/* Represents the type data of both native and user Godot Structs. + * StructInfo is itself exposed as a Godot Struct, so it serves as + * a good example for how to expose other structs. */ struct StructInfo { STRUCT_DECLARE(StructInfo); - STRUCT_MEMBER_PRIMITIVE(StringName, name, StringName()); - STRUCT_MEMBER_PRIMITIVE(int32_t, count, 0); - STRUCT_MEMBER_PRIMITIVE(Vector, names, Vector()); - STRUCT_MEMBER_PRIMITIVE_FROM_TO(Vector, types, Vector()); - STRUCT_MEMBER_PRIMITIVE(Vector, class_names, Vector()); - STRUCT_MEMBER_PRIMITIVE(Vector, default_values, Vector()); - STRUCT_LAYOUT(StructInfo, struct name, struct count, struct names, struct types, struct class_names, struct default_values); + STRUCT_MEMBER(StringName, name, StringName()); + STRUCT_MEMBER(int32_t, count, 0); + STRUCT_MEMBER(Vector, names, Vector()); + STRUCT_MEMBER_FROM_TO(Vector, types, Vector()); + STRUCT_MEMBER(Vector, class_names, Vector()); + STRUCT_MEMBER(Vector, default_values, Vector()); + STRUCT_LAYOUT_ALIAS("StructInfo", StructInfo, struct name, struct count, struct names, struct types, struct class_names, struct default_values); Vector struct_member_infos; diff --git a/tests/core/variant/test_struct.h b/tests/core/variant/test_struct.h index bc16a2c2efda..2c54a7fbf19b 100644 --- a/tests/core/variant/test_struct.h +++ b/tests/core/variant/test_struct.h @@ -271,9 +271,9 @@ TEST_CASE("[Struct] StructInfo") { TEST_CASE("[Struct] Validation") { struct NamedInt { STRUCT_DECLARE(NamedInt); - STRUCT_MEMBER_PRIMITIVE(String, name, String()); - STRUCT_MEMBER_PRIMITIVE(int, value, 0); - STRUCT_LAYOUT(NamedInt, struct name, struct value); + STRUCT_MEMBER(String, name, String()); + STRUCT_MEMBER(int, value, 0); + STRUCT_LAYOUT(TestStruct, NamedInt, struct name, struct value); }; Struct named_int; @@ -329,22 +329,22 @@ TEST_CASE("[Struct] Validation") { TEST_CASE("[Struct] Nesting") { struct BasicStruct { STRUCT_DECLARE(BasicStruct); - STRUCT_MEMBER_PRIMITIVE(int, int_val, 4); - STRUCT_MEMBER_PRIMITIVE(float, float_val, 5.5f); - STRUCT_LAYOUT(BasicStruct, struct int_val, struct float_val); + STRUCT_MEMBER(int, int_val, 4); + STRUCT_MEMBER(float, float_val, 5.5f); + STRUCT_LAYOUT(TestStruct, BasicStruct, struct int_val, struct float_val); BasicStruct() {}; }; struct BasicStructLookalike { STRUCT_DECLARE(BasicStructLookalike); - STRUCT_MEMBER_PRIMITIVE(int, int_val, 4); - STRUCT_MEMBER_PRIMITIVE(float, float_val, 5.5f); - STRUCT_LAYOUT(BasicStructLookalike, struct int_val, struct float_val); + STRUCT_MEMBER(int, int_val, 4); + STRUCT_MEMBER(float, float_val, 5.5f); + STRUCT_LAYOUT(TestStruct, BasicStructLookalike, struct int_val, struct float_val); }; struct NestedStruct { STRUCT_DECLARE(NestedStruct); STRUCT_MEMBER_CLASS_POINTER(Node, node, nullptr); STRUCT_MEMBER_STRUCT(BasicStruct, value, BasicStruct()); - STRUCT_LAYOUT(NestedStruct, struct node, struct value); + STRUCT_LAYOUT(TestStruct, NestedStruct, struct node, struct value); }; REQUIRE_EQ(NestedStruct::Layout::struct_member_count, 2);