// Copyright (C) 2022 Jérôme "Lynix" Leclercq (lynix680@gmail.com) // This file is part of the "Nazara Engine - Core module" // For conditions of distribution and use, see copyright notice in Config.hpp // http://stackoverflow.com/questions/687490/c0x-how-do-i-expand-a-tuple-into-variadic-template-function-arguments // Merci à Ryan "FullMetal Alchemist" Lahfa // Merci aussi à Freedom de siteduzero.com #include #include #include #include #include #include #include #include #include #include #include #include namespace Nz { namespace Detail { // http://www.cppsamples.com/common-tasks/apply-tuple-to-function.html template decltype(auto) ApplyImplFunc(F&& fn, Tuple&& t, std::index_sequence) { return std::forward(fn)(std::get(std::forward(t))...); } template decltype(auto) ApplyImplMethod(O& object, F&& fn, Tuple&& t, std::index_sequence) { return (object .* std::forward(fn))(std::get(std::forward(t))...); } NAZARA_CORE_API extern const UInt8 BitReverseTable256[256]; // https://stackoverflow.com/questions/28675727/using-crc32-algorithm-to-hash-string-at-compile-time // Generates CRC-32 table, algorithm based from this link: // http://www.hackersdelight.org/hdcodetxt/crc.c.txt constexpr auto GenerateCRC32Table(UInt32 polynomial = 0xEDB88320) { #ifdef NAZARA_COMPILER_MSVC // Disable warning: unary minus operator applied to unsigned type, result still unsigned #pragma warning(push) #pragma warning(disable: 4146) #endif constexpr UInt32 byteCount = 256; constexpr UInt32 iterationCount = 8; std::array crc32Table{}; for (UInt32 byte = 0u; byte < byteCount; ++byte) { UInt32 crc = byte; for (UInt32 i = 0; i < iterationCount; ++i) { UInt32 mask = static_cast(-(crc & 1)); crc = (crc >> 1) ^ (polynomial & mask); } crc32Table[byte] = crc; } return crc32Table; #ifdef NAZARA_COMPILER_MSVC #pragma warning(pop) #endif } // Stores CRC-32 table and softly validates it. static constexpr auto crc32Table = GenerateCRC32Table(); static_assert( crc32Table.size() == 256 && crc32Table[1] == 0x77073096 && crc32Table[255] == 0x2D02EF8D, "gen_crc32_table generated unexpected result." ); } /*! * \ingroup core * \brief Access a non-typed struct field by offset * \return A pointer to the field of the asked type * * \param basePtr Pointer to the start of the struct * \param offset Offset to the field (as generated by offsetof or similar) */ template decltype(auto) AccessByOffset(void* basePtr, std::size_t offset) { if constexpr (std::is_lvalue_reference_v) return *reinterpret_cast*>(static_cast(basePtr) + offset); else if constexpr (std::is_pointer_v) return reinterpret_cast(static_cast(basePtr) + offset); else static_assert(AlwaysFalse(), "AccessByOffset requires a reference or pointer type"); } /*! * \ingroup core * \brief Access a non-typed struct field by offset * \return A pointer to the field of the asked type * * \param basePtr Pointer to the start of the struct * \param offset Offset to the field (as generated by offsetof or similar) */ template decltype(auto) AccessByOffset(const void* basePtr, std::size_t offset) { static_assert(std::is_lvalue_reference_v || std::is_pointer_v); if constexpr (std::is_lvalue_reference_v) return *reinterpret_cast*>(static_cast(basePtr) + offset); else if constexpr (std::is_pointer_v) return reinterpret_cast(static_cast(basePtr) + offset); else static_assert(AlwaysFalse(), "AccessByOffset requires a reference or pointer type"); } /*! * \ingroup core * \brief Align an offset * \return Aligned offset according to alignment * * \param offset Base offset * \param alignment Non-zero alignment * * \see AlignPow2 */ template constexpr T Align(T offset, T alignment) { assert(alignment > 0); return ((offset + alignment - 1) / alignment) * alignment; } /*! * \ingroup core * \brief Align an offset * \return Aligned offset according to a power of two alignment * * \param offset Base offset * \param alignment Non-zero power of two alignment * * \see Align * \remark This function is quicker than Align but only works with power of two alignment values */ template constexpr T AlignPow2(T offset, T alignment) { assert(alignment > 0); assert(IsPowerOfTwo(alignment)); return (offset + alignment - 1) & ~(alignment - 1); } /*! * \ingroup core * \brief Applies the tuple to the function (e.g. calls the function using the tuple content as arguments) * \return The result of the function * * \param fn Function * \param t Tuple of arguments for the function * * \see Apply */ template decltype(auto) Apply(F&& fn, Tuple&& t) { constexpr std::size_t tSize = std::tuple_size::type>::value; return Detail::ApplyImplFunc(std::forward(fn), std::forward(t), std::make_index_sequence()); } /*! * \ingroup core * \brief Applies the tuple to the member function on an object (e.g. calls the member function using the tuple content as arguments) * \return The result of the member function called * * \param object Object of a class * \param fn Member function * \param t Tuple of arguments for the member function * * \see Apply */ template decltype(auto) Apply(O& object, F&& fn, Tuple&& t) { constexpr std::size_t tSize = std::tuple_size::type>::value; return Detail::ApplyImplMethod(object, std::forward(fn), std::forward(t), std::make_index_sequence()); } /*! * \ingroup core * \brief Returns the number of bits occupied by the type T * \return Number of bits occupied by the type */ template constexpr std::size_t BitCount() { return CHAR_BIT * sizeof(T); } /*! * \ingroup core * \brief Computes the hash of a hashable object * \return A bytearray which represents the hash * * \param hash Enumeration of type HashType * \param v Object to hash * * \remark a HashAppend specialization for type T is required * * \see ComputeHash */ template ByteArray ComputeHash(HashType hash, const T& v) { return ComputeHash(*AbstractHash::Get(hash), v); } /*! * \ingroup core * \brief Computes the hash of a hashable object * \return A bytearray which represents the hash * * \param hash Pointer to abstract hash * \param v Object to hash * * \remark Produce a NazaraAssert if pointer to Abstracthash is invalid * \remark a HashAppend specialization for type T is required * * \see ComputeHash */ template ByteArray ComputeHash(AbstractHash& hash, const T& v) { hash.Begin(); HashAppend(hash, v); return hash.End(); } // From https://stackoverflow.com/questions/28675727/using-crc32-algorithm-to-hash-string-at-compile-time constexpr UInt32 CRC32(const UInt8* input, std::size_t size) noexcept { UInt32 crc = 0xFFFFFFFFu; for (std::size_t i = 0u; i < size; ++i) crc = Detail::crc32Table[(crc ^ input[i]) & 0xFF] ^ (crc >> 8); return ~crc; } constexpr UInt32 CRC32(const char* str) noexcept { UInt32 crc = 0xFFFFFFFFu; for (std::size_t i = 0u; auto c = str[i]; ++i) crc = Detail::crc32Table[(crc ^ str[i]) & 0xFF] ^ (crc >> 8); return ~crc; } constexpr UInt32 CRC32(const std::string_view& str) noexcept { UInt32 crc = 0xFFFFFFFFu; for (std::size_t i = 0u; i < str.size(); ++i) crc = Detail::crc32Table[(crc ^ str[i]) & 0xFF] ^ (crc >> 8); return ~crc; } template constexpr UInt32 CRC32(const char (&str)[N]) noexcept { UInt32 crc = 0xFFFFFFFFu; for (std::size_t i = 0u; i < N - 1; ++i) crc = Detail::crc32Table[(crc ^ str[i]) & 0xFF] ^ (crc >> 8); return ~crc; } /*! * \ingroup core * \brief Returns the number of elements in a C-array * \return The number of elements * * \see CountOf */ template constexpr std::size_t CountOf(T(&)[N]) noexcept { return N; } /*! * \ingroup core * \brief Returns the number of elements in a container * \return The number of elements * * \param c Container with the member function "size()" * * \see CountOf */ template std::size_t CountOf(const T& c) { return c.size(); } inline bool HashAppend(AbstractHash& hash, const std::string_view& v) { hash.Append(reinterpret_cast(v.data()), v.size()); return true; } /*! * \ingroup core * \brief Combines two hash in one * * \param seed First value that will be modified (expected to be 64bits) * \param v Second value to hash */ // Algorithm from CityHash by Google // http://stackoverflow.com/questions/8513911/how-to-create-a-good-hash-combine-with-64-bit-output-inspired-by-boosthash-co template void HashCombine(std::size_t& seed, const T& v) { const UInt64 kMul = 0x9ddfea08eb382d69ULL; std::hash hasher; UInt64 a = (hasher(v) ^ seed) * kMul; a ^= (a >> 47); UInt64 b = (seed ^ a) * kMul; b ^= (b >> 47); seed = static_cast(b * kMul); } /*! * \ingroup core * \brief Check if a value is a power of two * \return true if value is a power of two * * \param value Non-zero value */ template bool IsPowerOfTwo(T value) { assert(value != 0); return (value & (value - 1)) == 0; } /*! * \ingroup core * \brief Reverse the bit order of the integer * \return integer with reversed bits * * \param integer Integer whose bits are to be reversed */ template T ReverseBits(T integer) { T reversed = 0; for (std::size_t i = 0; i < sizeof(T); ++i) reversed |= T(Detail::BitReverseTable256[(integer >> i * 8) & 0xFF]) << (sizeof(T) * 8 - (i + 1) * 8); return reversed; } template To SafeCast(From&& value) { #ifdef NAZARA_COMPILER_MSVC // Disable unreachable code warnings #pragma warning(push) #pragma warning(disable: 4702) #endif #if defined(NAZARA_DEBUG) && !defined(NDEBUG) if constexpr (std::is_integral_v) { if constexpr (std::is_enum_v) { return SafeCast(static_cast>(value)); } else if constexpr (std::is_floating_point_v) { assert(std::floor(value) == value); assert(value <= static_cast(std::numeric_limits::max())); assert(value >= static_cast(std::numeric_limits::lowest())); } else if constexpr (std::is_integral_v) { // Type capable of storing the biggest value between the two types using MaxValueType = std::conditional_t<(sizeof(From) > sizeof(To) || (sizeof(From) == sizeof(To) && std::is_signed_v)), From, To>; // Type capable of storing the smallest value between the two types using MinValueType = std::conditional_t<(sizeof(From) > sizeof(To) || (sizeof(From) == sizeof(To) && std::is_signed_v)), From, To>; if constexpr (!std::is_signed_v) assert(value >= 0); assert(static_cast(value) <= static_cast(std::numeric_limits::max())); assert(static_cast(value) >= static_cast(std::numeric_limits::lowest())); } } else if constexpr (std::is_enum_v) { return static_cast(SafeCast>(value)); } else if constexpr (std::is_floating_point_v) { if constexpr (std::is_floating_point_v) { // Type capable of storing the biggest value between the two types using MaxValueType = std::conditional_t<(sizeof(From) > sizeof(To)), From, To>; // Type capable of storing the smallest value between the two types using MinValueType = std::conditional_t<(sizeof(From) > sizeof(To)), From, To>; assert(static_cast(value) <= static_cast(std::numeric_limits::max())); assert(static_cast(value) >= static_cast(std::numeric_limits::lowest())); } } else if constexpr (std::is_reference_v) { if constexpr (std::is_reference_v) { using BaseFromType = std::remove_reference_t>; using BaseToType = std::remove_reference_t>; if constexpr (!std::is_same_v && std::is_base_of_v && std::is_polymorphic_v) { using ToPtr = std::add_pointer_t>; assert(dynamic_cast(&value) != nullptr); } } } else if constexpr (std::is_pointer_v) { if constexpr (std::is_pointer_v) { using BaseFromType = std::remove_pointer_t>; using BaseToType = std::remove_pointer_t>; if constexpr (!std::is_same_v && std::is_base_of_v && std::is_polymorphic_v) { assert(dynamic_cast(value) != nullptr); } } } #endif return static_cast(value); #ifdef NAZARA_COMPILER_MSVC #pragma warning(pop) #endif } template constexpr auto UnderlyingCast(T value) -> std::underlying_type_t { return static_cast>(value); } template struct PointedType { using type = T; }; template struct PointedType { using type = T; }; template struct PointedType { using type = T; }; template struct PointedType { using type = T; }; template bool Serialize(SerializationContext& context, T&& value) { return Serialize(context, std::forward(value), TypeTag>()); } /*! * \ingroup core * \brief Serializes a boolean * \return true if serialization succeeded * * \param context Context for the serialization * \param value Boolean to serialize * * \see Serialize, Unserialize */ inline bool Serialize(SerializationContext& context, bool value, TypeTag) { if (context.writeBitPos == 8) { context.writeBitPos = 0; context.writeByte = 0; } if (value) context.writeByte |= 1 << context.writeBitPos; if (++context.writeBitPos >= 8) return Serialize(context, context.writeByte, TypeTag()); else return true; } /*! * \ingroup core * \brief Serializes a std::string * \return true if successful * * \param context Context for the serialization * \param value String to serialize */ bool Serialize(SerializationContext& context, const std::string& value, TypeTag) { if (!Serialize(context, UInt32(value.size()), TypeTag())) return false; return context.stream->Write(value.data(), value.size()) == value.size(); } /*! * \ingroup core * \brief Serializes an arithmetic type * \return true if serialization succeeded * * \param context Context for the serialization * \param value Arithmetic type to serialize * * \see Serialize, Unserialize */ template std::enable_if_t::value, bool> Serialize(SerializationContext& context, T value, TypeTag) { // Flush bits in case a writing is in progress context.FlushBits(); if (context.endianness != Endianness::Unknown && context.endianness != GetPlatformEndianness()) SwapBytes(&value, sizeof(T)); return context.stream->Write(&value, sizeof(T)) == sizeof(T); } template bool Unserialize(SerializationContext& context, T* value) { return Unserialize(context, value, TypeTag()); } /*! * \ingroup core * \brief Unserializes a boolean * \return true if unserialization succedeed * * \param context Context for the unserialization * \param value Pointer to boolean to unserialize * * \see Serialize, Unserialize */ inline bool Unserialize(SerializationContext& context, bool* value, TypeTag) { if (context.readBitPos == 8) { if (!Unserialize(context, &context.readByte, TypeTag())) return false; context.readBitPos = 0; } if (value) *value = (context.readByte & (1 << context.readBitPos)) != 0; context.readBitPos++; return true; } /*! * \brief Unserializes a string * \return true if successful * * \param context Context of unserialization * \param string std::string to unserialize */ bool Unserialize(SerializationContext& context, std::string* string, TypeTag) { UInt32 size; if (!Unserialize(context, &size, TypeTag())) return false; string->resize(size); return context.stream->Read(&(*string)[0], size) == size; } /*! * \ingroup core * \brief Unserializes an arithmetic type * \return true if unserialization succedeed * * \param context Context for the unserialization * \param value Pointer to arithmetic type to serialize * * \remark Produce a NazaraAssert if pointer to value is invalid * * \see Serialize, Unserialize */ template std::enable_if_t::value, bool> Unserialize(SerializationContext& context, T* value, TypeTag) { NazaraAssert(value, "Invalid data pointer"); context.ResetReadBitPosition(); if (context.stream->Read(value, sizeof(T)) == sizeof(T)) { if (context.endianness != Endianness::Unknown && context.endianness != GetPlatformEndianness()) SwapBytes(value, sizeof(T)); return true; } else return false; } } #include