From 72b664f42c8e9889117ae8bff611fee64e67f218 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Leclercq?= Date: Fri, 4 Mar 2022 20:40:41 +0100 Subject: [PATCH] Core: Add Uuid class --- include/Nazara/Core.hpp | 1 + include/Nazara/Core/Uuid.hpp | 66 ++++++++++++++++++++ include/Nazara/Core/Uuid.inl | 110 +++++++++++++++++++++++++++++++++ src/Nazara/Core/Uuid.cpp | 65 +++++++++++++++++++ tests/Engine/Core/UuidTest.cpp | 60 ++++++++++++++++++ xmake.lua | 6 +- 6 files changed, 306 insertions(+), 2 deletions(-) create mode 100644 include/Nazara/Core/Uuid.hpp create mode 100644 include/Nazara/Core/Uuid.inl create mode 100644 src/Nazara/Core/Uuid.cpp create mode 100644 tests/Engine/Core/UuidTest.cpp diff --git a/include/Nazara/Core.hpp b/include/Nazara/Core.hpp index a3d256e34..52cf40b48 100644 --- a/include/Nazara/Core.hpp +++ b/include/Nazara/Core.hpp @@ -93,5 +93,6 @@ #include #include #include +#include #endif // NAZARA_GLOBAL_CORE_HPP diff --git a/include/Nazara/Core/Uuid.hpp b/include/Nazara/Core/Uuid.hpp new file mode 100644 index 000000000..ee9b353d2 --- /dev/null +++ b/include/Nazara/Core/Uuid.hpp @@ -0,0 +1,66 @@ +// Copyright (C) 2022 Full Cycle Games +// This file is part of the "Nazara Engine - Core module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_CORE_UUID_HPP +#define NAZARA_CORE_UUID_HPP + +#include +#include +#include +#include +#include +#include + +namespace Nz +{ + class NAZARA_CORE_API Uuid + { + public: + inline Uuid(); + inline Uuid(const std::array guid); + Uuid(const Uuid&) = default; + Uuid(Uuid&& generator) = default; + ~Uuid() = default; + + inline bool IsNull() const; + + inline const std::array& ToArray() const; + inline std::string ToString() const; + std::array ToStringArray() const; + + Uuid& operator=(const Uuid&) = default; + Uuid& operator=(Uuid&&) = default; + + static Uuid Generate(); + + private: + std::array m_guid; + }; + + NAZARA_CORE_API std::ostream& operator<<(std::ostream& out, const Uuid& guid); + inline bool operator==(const Uuid& lhs, const Uuid& rhs); + inline bool operator!=(const Uuid& lhs, const Uuid& rhs); + inline bool operator<(const Uuid& lhs, const Uuid& rhs); + inline bool operator<=(const Uuid& lhs, const Uuid& rhs); + inline bool operator>(const Uuid& lhs, const Uuid& rhs); + inline bool operator>=(const Uuid& lhs, const Uuid& rhs); +} + +namespace Nz +{ + inline bool Serialize(SerializationContext& context, const Uuid& value, TypeTag); + inline bool Unserialize(SerializationContext& context, Uuid* value, TypeTag); +} + +namespace std +{ + template<> + struct hash; +} + +#include + +#endif // NAZARA_CORE_UUID_HPP diff --git a/include/Nazara/Core/Uuid.inl b/include/Nazara/Core/Uuid.inl new file mode 100644 index 000000000..5fc081512 --- /dev/null +++ b/include/Nazara/Core/Uuid.inl @@ -0,0 +1,110 @@ +// Copyright (C) 2022 Full Cycle Games +// This file is part of the "Nazara Engine - Core module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include + +namespace Nz +{ + inline Uuid::Uuid() + { + m_guid.fill(0); + } + + inline Uuid::Uuid(const std::array guid) : + m_guid(guid) + { + } + + inline bool Uuid::IsNull() const + { + Uuid NullGuid; + return *this == NullGuid; + } + + inline const std::array& Uuid::ToArray() const + { + return m_guid; + } + + inline std::string Uuid::ToString() const + { + std::array guidStr = ToStringArray(); + + return std::string(guidStr.data(), guidStr.size() - 1); + } + + bool operator==(const Uuid& lhs, const Uuid& rhs) + { + return lhs.ToArray() == rhs.ToArray(); + } + + bool operator!=(const Uuid& lhs, const Uuid& rhs) + { + return lhs.ToArray() != rhs.ToArray(); + } + + bool operator<(const Uuid& lhs, const Uuid& rhs) + { + return lhs.ToArray() < rhs.ToArray(); + } + + bool operator<=(const Uuid& lhs, const Uuid& rhs) + { + return lhs.ToArray() <= rhs.ToArray(); + } + + bool operator>(const Uuid& lhs, const Uuid& rhs) + { + return lhs.ToArray() > rhs.ToArray(); + } + + bool operator>=(const Uuid& lhs, const Uuid& rhs) + { + return lhs.ToArray() >= rhs.ToArray(); + } +} + +namespace Nz +{ + bool Serialize(SerializationContext& context, const Uuid& value, TypeTag) + { + const std::array& array = value.ToArray(); + if (context.stream->Write(array.data(), array.size()) != array.size()) + return false; + + return true; + } + + bool Unserialize(SerializationContext& context, Uuid* value, TypeTag) + { + std::array array; + if (context.stream->Read(array.data(), array.size()) != array.size()) + return false; + + *value = Uuid(array); + return true; + } +} + +namespace std +{ + template <> + struct hash + { + size_t operator()(const Nz::Uuid& guid) const + { + // DJB2 algorithm http://www.cse.yorku.ca/~oz/hash.html + size_t h = 5381; + + const array& data = guid.ToArray(); + for (size_t i = 0; i < data.size(); ++i) + h = ((h << 5) + h) + data[i]; + + return h; + } + }; +} + +#include diff --git a/src/Nazara/Core/Uuid.cpp b/src/Nazara/Core/Uuid.cpp new file mode 100644 index 000000000..701f603d7 --- /dev/null +++ b/src/Nazara/Core/Uuid.cpp @@ -0,0 +1,65 @@ +// Copyright (C) 2022 Full Cycle Games +// This file is part of the "Nazara Engine - Core module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include + +#ifdef NAZARA_PLATFORM_WINDOWS +#include +#elif defined(NAZARA_PLATFORM_LINUX) || defined(NAZARA_PLATFORM_MACOSX) +#include +#endif + +#include + +namespace Nz +{ + std::array Uuid::ToStringArray() const + { + std::array guidStr; //< Including \0 + std::snprintf(guidStr.data(), guidStr.size(), "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", + m_guid[0], m_guid[1], m_guid[2], m_guid[3], m_guid[4], m_guid[5], m_guid[6], m_guid[7], + m_guid[8], m_guid[9], m_guid[10], m_guid[11], m_guid[12], m_guid[13], m_guid[14], m_guid[15]); + + return guidStr; + } + + Uuid Uuid::Generate() + { + std::array guid; + +#ifdef NAZARA_PLATFORM_WINDOWS + GUID id; + CoCreateGuid(&id); + + for (unsigned int i = 0; i < 4; ++i) + guid[i] = static_cast(id.Data1 >> ((3 - i) * 8 & 0xFF)); + + for (unsigned int i = 0; i < 2; ++i) + guid[4 + i] = static_cast(id.Data2 >> ((1 - i) * 8 & 0xFF)); + + for (unsigned int i = 0; i < 2; ++i) + guid[6 + i] = static_cast(id.Data3 >> ((1 - i) * 8 & 0xFF)); + + for (unsigned int i = 0; i < 8; ++i) + guid[8 + i] = static_cast(id.Data4[i]); +#elif defined(NAZARA_PLATFORM_LINUX) || defined(NAZARA_PLATFORM_MACOSX) + uuid_t id; + uuid_generate(id); + + std::copy(std::begin(id), std::end(id), guid.begin()); +#else +#error Missing platform support +#endif + + return guid; + } + + std::ostream& operator<<(std::ostream& out, const Uuid& guid) + { + std::array guidStr = guid.ToStringArray(); + + return out << guidStr.data(); + } +} diff --git a/tests/Engine/Core/UuidTest.cpp b/tests/Engine/Core/UuidTest.cpp new file mode 100644 index 000000000..0f4a5b616 --- /dev/null +++ b/tests/Engine/Core/UuidTest.cpp @@ -0,0 +1,60 @@ +#include +#include +#include +#include +#include + +SCENARIO("Uuid", "[CORE][UUID]") +{ + WHEN("Generating a null UUID") + { + Nz::Uuid nullUuid; + CHECK(nullUuid.IsNull()); + CHECK(nullUuid.ToArray() == std::array{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}); + CHECK(nullUuid.ToString() == "00000000-0000-0000-0000-000000000000"); + CHECK(nullUuid.ToStringArray() == std::array{"00000000-0000-0000-0000-000000000000"}); + CHECK(nullUuid == Nz::Uuid{}); + CHECK(nullUuid >= Nz::Uuid{}); + CHECK(nullUuid <= Nz::Uuid{}); + CHECK_FALSE(nullUuid > Nz::Uuid{}); + CHECK_FALSE(nullUuid < Nz::Uuid{}); + CHECK(nullUuid != Nz::Uuid::Generate()); + } + + WHEN("Generating a UUID") + { + // https://stackoverflow.com/questions/136505/searching-for-uuids-in-text-with-regex + std::regex uuidRegex(R"(^\b[0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12}\b$)"); + + Nz::Uuid uuid = Nz::Uuid::Generate(); + CHECK_FALSE(uuid.IsNull()); + CHECK(std::regex_match(uuid.ToString(), uuidRegex)); + CHECK(uuid == uuid); + CHECK(uuid == Nz::Uuid{uuid.ToArray()}); + CHECK(uuid >= uuid); + CHECK(uuid <= uuid); + CHECK_FALSE(uuid > uuid); + CHECK_FALSE(uuid < uuid); + } + + WHEN("Generating multiple UUID, they are unique") + { + std::set uuidSet; + std::unordered_set uuidUnorderedset; + + auto InsertUniqueUuid = [](auto& container, const Nz::Uuid& uuid) + { + auto it = container.find(uuid); + REQUIRE(it == container.end()); + container.insert(uuid); + }; + + for (std::size_t i = 0; i < 1'000; ++i) + { + auto uuid = Nz::Uuid::Generate(); + + InsertUniqueUuid(uuidSet, uuid); + InsertUniqueUuid(uuidUnorderedset, uuid); + } + } +} diff --git a/xmake.lua b/xmake.lua index a8e456664..733a180f1 100644 --- a/xmake.lua +++ b/xmake.lua @@ -12,8 +12,10 @@ local modules = { -- NazaraMath is header-only, make it part of the core project add_headerfiles("include/(Nazara/Math/**.hpp)", "include/(Nazara/Math/**.inl)") - if is_plat("linux") then - add_syslinks("dl", "pthread") + if is_plat("windows") then + add_syslinks("ole32") + elseif is_plat("linux") then + add_syslinks("dl", "pthread", "uuid") end end, Packages = {"entt"}