| // Copyright 2022 The Dawn & Tint Authors | |
| // | |
| // Redistribution and use in source and binary forms, with or without | |
| // modification, are permitted provided that the following conditions are met: | |
| // | |
| // 1. Redistributions of source code must retain the above copyright notice, this | |
| // list of conditions and the following disclaimer. | |
| // | |
| // 2. Redistributions in binary form must reproduce the above copyright notice, | |
| // this list of conditions and the following disclaimer in the documentation | |
| // and/or other materials provided with the distribution. | |
| // | |
| // 3. Neither the name of the copyright holder nor the names of its | |
| // contributors may be used to endorse or promote products derived from | |
| // this software without specific prior written permission. | |
| // | |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
| // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
| // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
| // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
| // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
| // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
| // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| #ifndef SRC_TINT_UTILS_REFLECTION_REFLECTION_H_ | |
| #define SRC_TINT_UTILS_REFLECTION_REFLECTION_H_ | |
| #include <cstddef> | |
| #include <string> | |
| #include <string_view> | |
| #include <tuple> | |
| #include <type_traits> | |
| #include <utility> | |
| #include "src/tint/utils/containers/vector.h" | |
| #include "src/tint/utils/macros/foreach.h" | |
| #include "src/tint/utils/macros/scoped_assignment.h" | |
| #include "src/tint/utils/memory/aligned_storage.h" | |
| #include "src/tint/utils/result.h" | |
| /// Forward declarations | |
| namespace tint { | |
| class CastableBase; | |
| } | |
| namespace tint::reflection::detail { | |
| /// Helper for detecting whether the type T contains a nested Reflection class. | |
| template <typename T, typename ENABLE = void> | |
| struct HasReflection : std::false_type {}; | |
| /// Specialization for types that have a nested Reflection class. | |
| template <typename T> | |
| struct HasReflection<T, std::void_t<typename T::Reflection>> : std::true_type {}; | |
| /// Helper for inferring the base class size of T. | |
| template <typename T, typename ENABLE = void> | |
| struct BaseClassSize { | |
| /// Zero as T::Base does not exist | |
| static constexpr size_t value = 0; | |
| }; | |
| /// Specialization for types that contain a 'Base' type alias. | |
| template <typename T> | |
| struct BaseClassSize<T, std::void_t<typename T::Base>> { | |
| /// The size of T::Base, or zero if T::Base is not a base of T | |
| static constexpr size_t value = | |
| std::is_base_of_v<typename T::Base, T> ? sizeof(typename T::Base) : 0; | |
| }; | |
| /// ReflectedFieldInfo describes a single reflected field. Used by CheckAllFieldsReflected() | |
| struct ReflectedFieldInfo { | |
| /// The field name | |
| const std::string_view name; | |
| /// The field size in bytes | |
| const size_t size; | |
| /// The field alignment in bytes | |
| const size_t align; | |
| /// The field offset in bytes | |
| const size_t offset; | |
| }; | |
| /// @returns Success if the sequential fields of @p fields have the expected offsets, and aligned | |
| /// sum match the class size @p class_size. | |
| Result<SuccessType> CheckAllFieldsReflected(VectorRef<ReflectedFieldInfo> fields, | |
| std::string_view class_name, | |
| size_t class_size, | |
| size_t class_align, | |
| bool class_is_castable); | |
| /// @returns Success if the TINT_REFLECT() reflected fields of @tparam CLASS match the declaration | |
| /// order, do not have any gaps, and fully account for the entire size of the class. | |
| template <typename CLASS> | |
| Result<SuccessType> CheckAllFieldsReflected() { | |
| static_assert(!std::has_virtual_destructor_v<CLASS> || std::is_base_of_v<CastableBase, CLASS>, | |
| "TINT_ASSERT_ALL_FIELDS_REFLECTED() cannot be used on virtual classes, except " | |
| "for types using the tint::Castable framework"); | |
| using R = typename CLASS::Reflection; | |
| using Fields = typename R::Fields; | |
| Vector<ReflectedFieldInfo, std::tuple_size_v<Fields>> fields; | |
| AlignedStorage<CLASS> obj{}; | |
| R::ForeachField(obj.Get(), [&](auto&& field, std::string_view name) { | |
| using T = std::decay_t<decltype(field)>; | |
| size_t offset = static_cast<size_t>(reinterpret_cast<const std::byte*>(&field) - | |
| reinterpret_cast<const std::byte*>(&obj)); | |
| fields.Push({name, sizeof(T), alignof(T), offset}); | |
| }); | |
| return CheckAllFieldsReflected(fields, R::Name, sizeof(CLASS), alignof(CLASS), | |
| std::is_base_of_v<CastableBase, CLASS>); | |
| } | |
| } // namespace tint::reflection::detail | |
| namespace tint { | |
| /// Is true if the class T has reflected its fields with TINT_REFLECT() | |
| template <typename T> | |
| concept HasReflection = tint::reflection::detail::HasReflection<T>::value; | |
| /// Calls @p callback with each field of @p object | |
| /// @param object the object | |
| /// @param callback a function that is called for each field of @p object. | |
| /// @tparam CB a function with one of the signatures: | |
| /// `void(auto& FIELD)` | |
| /// `void(auto& FIELD, std::string_view NAME)` | |
| template <typename OBJECT, typename CB> | |
| void ForeachField(OBJECT& object, CB&& callback) { | |
| using T = std::decay_t<OBJECT>; | |
| static_assert(HasReflection<T>, "object missing TINT_REFLECT() declaration"); | |
| constexpr bool callback_field = std::is_invocable_v<std::decay_t<CB>, int&>; | |
| constexpr bool callback_field_name = | |
| std::is_invocable_v<std::decay_t<CB>, int&, std::string_view>; | |
| static_assert(callback_field || callback_field_name, | |
| "callback must have the signature of:\n" | |
| " 'void(auto& field)'\n" | |
| "or 'void(auto& field, std::string_view name)'"); | |
| if constexpr (callback_field) { | |
| T::Reflection::ForeachField(object, | |
| [&](auto& field, std::string_view) { callback(field); }); | |
| } else { | |
| T::Reflection::ForeachField(object, std::forward<CB>(callback)); | |
| } | |
| } | |
| /// Calls @p callback with each field of @p object | |
| /// @param object the object | |
| /// @param callback a function that is called for each field of @p object. | |
| /// @tparam CB a function with one of the signatures: | |
| /// `void(const auto& FIELD)` | |
| /// `void(const auto& FIELD, std::string_view NAME)` | |
| template <typename OBJECT, typename CB> | |
| void ForeachField(const OBJECT& object, CB&& callback) { | |
| using T = std::decay_t<OBJECT>; | |
| static_assert(HasReflection<T>, "object missing TINT_REFLECT() declaration"); | |
| constexpr bool callback_field = std::is_invocable_v<std::decay_t<CB>, const int&>; | |
| constexpr bool callback_field_name = | |
| std::is_invocable_v<std::decay_t<CB>, const int&, std::string_view>; | |
| static_assert(callback_field || callback_field_name, | |
| "callback must have the signature of:\n" | |
| " 'void(auto& field)'\n" | |
| "or 'void(auto& field, std::string_view name)'"); | |
| if constexpr (callback_field) { | |
| T::Reflection::ForeachField(object, | |
| [&](const auto& field, std::string_view) { callback(field); }); | |
| } else { | |
| T::Reflection::ForeachField(object, std::forward<CB>(callback)); | |
| } | |
| } | |
| /// Forward declaration of the recursive dispatcher for printing reflected values. | |
| template <typename STREAM, typename T> | |
| requires(traits::IsOStream<STREAM>) | |
| void PrintValue(STREAM& ss, const T& value, std::string indent); | |
| /// Helper to print a reflected struct. | |
| template <typename STREAM, typename T> | |
| requires(traits::IsOStream<STREAM> && HasReflection<T>) | |
| void PrintReflected(STREAM& ss, const T& value, std::string indent = "") { | |
| ss << T::Reflection::Name << "{\n"; | |
| { | |
| TINT_SCOPED_ASSIGNMENT(indent, indent + " "); | |
| ForeachField(value, [&](const auto& field, std::string_view name) { | |
| ss << indent << name << ": "; | |
| PrintValue(ss, field, indent); | |
| ss << "\n"; | |
| }); | |
| } | |
| ss << indent << "}"; | |
| } | |
| /// Main dispatcher function for formatting various reflected types. | |
| template <typename STREAM, typename T> | |
| requires(traits::IsOStream<STREAM>) | |
| void PrintValue(STREAM& ss, const T& value, std::string indent) { | |
| if constexpr (HasReflection<T>) { | |
| PrintReflected(ss, value, indent); | |
| } else if constexpr (std::is_same_v<std::decay_t<T>, bool>) { | |
| ss << (value ? "true" : "false"); | |
| } else if constexpr (std::is_integral_v<std::decay_t<T>> || | |
| std::is_floating_point_v<std::decay_t<T>>) { | |
| ss << value; | |
| } else if constexpr (requires { | |
| value.begin(); | |
| value.end(); | |
| }) { | |
| // Handle iterable containers (e.g. maps and vectors). | |
| ss << "["; | |
| for (const auto& item : value) { | |
| // If this is a map pair, print the key and value. | |
| if constexpr (requires { | |
| item.first; | |
| item.second; | |
| }) { | |
| TINT_SCOPED_ASSIGNMENT(indent, indent + " "); | |
| ss << "\n" << indent << "{\n"; | |
| { | |
| TINT_SCOPED_ASSIGNMENT(indent, indent + " "); | |
| ss << indent << "key: "; | |
| PrintValue(ss, item.first, indent); | |
| ss << "\n" << indent << "value: "; | |
| PrintValue(ss, item.second, indent); | |
| } | |
| ss << "\n" << indent << "}"; | |
| } else { | |
| PrintValue(ss, item, indent); | |
| } | |
| } | |
| ss << "]"; | |
| } else if constexpr (requires { *value; }) { | |
| // Handle std::optional<value>. | |
| if (value) { | |
| PrintValue(ss, *value, indent); | |
| } else { | |
| ss << "std::nullopt"; | |
| } | |
| } else if constexpr (requires(STREAM& os, const T& v) { os << v; }) { | |
| ss << value; | |
| } else { | |
| static_assert(sizeof(T) == 0, "unhandled type"); | |
| } | |
| } | |
| /// Macro used by TINT_FOREACH() in TINT_REFLECT() to generate the T::Reflection::Fields tuple. | |
| #define TINT_REFLECT_FIELD_TYPE(FIELD) decltype(Class::FIELD), | |
| /// Macro used by TINT_FOREACH() in TINT_REFLECT() to generate a tuple of member values. | |
| #define TINT_REFLECT_FIELD_VALUE(FIELD) object.FIELD, | |
| /// Macro used by TINT_FOREACH() in TINT_REFLECT() to call the callback function with each field in | |
| /// the variadic. | |
| #define TINT_REFLECT_CALLBACK_FIELD(FIELD) callback(object.FIELD, #FIELD); | |
| // TINT_REFLECT(CLASS, ...) reflects each of the fields arguments of CLASS so that the types can be | |
| // used with tint::ForeachField(). | |
| #define TINT_REFLECT(CLASS, ...) \ | |
| struct Reflection { \ | |
| using Class = CLASS; \ | |
| using Fields = std::tuple<TINT_FOREACH(TINT_REFLECT_FIELD_TYPE, __VA_ARGS__) void>; \ | |
| [[maybe_unused]] static constexpr std::string_view Name = #CLASS; \ | |
| [[maybe_unused]] static constexpr std::monostate kIgnore{}; \ | |
| template <typename OBJECT, typename CB> \ | |
| [[maybe_unused]] static constexpr void ForeachField(OBJECT&& object, CB&& callback) { \ | |
| TINT_FOREACH(TINT_REFLECT_CALLBACK_FIELD, __VA_ARGS__) \ | |
| } \ | |
| [[maybe_unused]] static constexpr auto FieldsAsTuple(const Class& object) { \ | |
| return std::tie(TINT_FOREACH(TINT_REFLECT_FIELD_VALUE, __VA_ARGS__) kIgnore); \ | |
| } \ | |
| } | |
| /// TINT_REFLECT_HASH_CODE(CLASS) generates a tint::HashCode implementation for the CLASS based on | |
| /// the reflected fields. | |
| #define TINT_REFLECT_HASH_CODE(CLASS) \ | |
| tint::HashCode HashCode() const { \ | |
| return Hash(CLASS::Reflection::FieldsAsTuple(*this)); \ | |
| } \ | |
| TINT_REQUIRE_SEMICOLON | |
| /// TINT_ASSERT_ALL_FIELDS_REFLECTED(...) performs a compile-time assertion that all the fields of | |
| /// CLASS have been reflected with TINT_REFLECT(). | |
| /// @note The order in which the fields are passed to TINT_REFLECT must match the declaration order | |
| /// in the class. | |
| #define TINT_ASSERT_ALL_FIELDS_REFLECTED(CLASS) \ | |
| ASSERT_EQ(::tint::reflection::detail::CheckAllFieldsReflected<CLASS>(), ::tint::Success) | |
| /// A template that can be specialized to reflect the valid range of an enum | |
| /// Use TINT_REFLECT_ENUM_RANGE to specialize this class | |
| template <typename T> | |
| struct EnumRange; | |
| /// Declares a specialization of EnumRange for the enum ENUM with the lowest enum value MIN and | |
| /// largest enum value MAX. Must only be used in the `tint` namespace. | |
| #define TINT_REFLECT_ENUM_RANGE(ENUM, MIN, MAX) \ | |
| template <> \ | |
| struct EnumRange<ENUM> { \ | |
| static constexpr ENUM kMin = ENUM::MIN; \ | |
| static constexpr ENUM kMax = ENUM::MAX; \ | |
| } | |
| } // namespace tint | |
| #endif // SRC_TINT_UTILS_REFLECTION_REFLECTION_H_ |