/************************************************************************************ * * D++, A Lightweight C++ library for Discord * * SPDX-License-Identifier: Apache-2.0 * Copyright 2021 Craig Edwards and D++ contributors * (https://github.com/brainboxdotcc/DPP/graphs/contributors) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ************************************************************************************/ #pragma once #include #include #include #include #include /** * @brief The main namespace for D++ functions. classes and types */ namespace dpp { /** @brief A container for a 64 bit unsigned value representing many things on discord. * This value is known in distributed computing as a snowflake value. * * Snowflakes are: * * - Performant (very fast to generate at source and to compare in code) * - Uncoordinated (allowing high availability across clusters, data centres etc) * - Time ordered (newer snowflakes have higher IDs) * - Directly Sortable (due to time ordering) * - Compact (64 bit numbers, not 128 bit, or string) * * An identical format of snowflake is used by Twitter, Instagram and several other platforms. * * @see https://en.wikipedia.org/wiki/Snowflake_ID * @see https://github.com/twitter-archive/snowflake/tree/b3f6a3c6ca8e1b6847baa6ff42bf72201e2c2231 */ class DPP_EXPORT snowflake final { friend struct std::hash; protected: /** * @brief The snowflake value */ uint64_t value = 0; public: /** * @brief Construct a snowflake object */ constexpr snowflake() noexcept = default; /** * @brief Copy a snowflake object */ constexpr snowflake(const snowflake &rhs) noexcept = default; /** * @brief Move a snowflake object */ constexpr snowflake(snowflake &&rhs) noexcept = default; /** * @brief Construct a snowflake from an integer value * * @throw dpp::logic_exception on assigning a negative value. The function is noexcept if the type given is unsigned * @param snowflake_val snowflake value as an integer type */ template && !std::is_same_v>> constexpr snowflake(T snowflake_val) noexcept(std::is_unsigned_v) : value(static_cast>(snowflake_val)) { /** * we cast to the unsigned version of the type given - this maintains "possible loss of data" warnings for sizeof(T) > sizeof(value) * while suppressing them for signed to unsigned conversion (for example snowflake(42) will call snowflake(int) which is a signed type) */ if constexpr (!std::is_unsigned_v) { /* if the type is signed, at compile-time, add a check at runtime that the value is unsigned */ if (snowflake_val < 0) { value = 0; throw dpp::logic_exception{"cannot assign a negative value to dpp::snowflake"}; } } } /** * @brief Construct a snowflake object from an unsigned integer in a string * * On invalid string the value will be 0 * @param string_value A snowflake value */ snowflake(std::string_view string_value) noexcept; /** * @brief Construct a snowflake object from an unsigned integer in a string * * On invalid string the value will be 0 * @param string_value A snowflake value */ template >> snowflake(const T &string_value) noexcept : snowflake(std::string_view{string_value}) {} /* ^ this exists to preserve `message_cache.find(std::get(event.get_parameter("message_id")));` */ /** * @brief Copy value from another snowflake * * @param rhs The snowflake to copy from */ constexpr dpp::snowflake &operator=(const dpp::snowflake& rhs) noexcept = default; /** * @brief Move value from another snowflake * * @param rhs The snowflake to move from */ constexpr dpp::snowflake &operator=(dpp::snowflake&& rhs) noexcept = default; /** * @brief Assign integer value to the snowflake * * @throw dpp::logic_exception on assigning a negative value. The function is noexcept if the type given is unsigned * @param snowflake_val snowflake value as an integer type */ template >> constexpr dpp::snowflake &operator=(T snowflake_val) noexcept(std::is_unsigned_v) { return *this = dpp::snowflake{snowflake_val}; } /** * @brief Assign value converted from a string to the snowflake * * On invalid string the value will be 0 * @param snowflake_val snowflake value as a string */ template >> constexpr dpp::snowflake &operator=(T&& snowflake_val) noexcept { return *this = dpp::snowflake{std::forward(snowflake_val)}; } /** * @brief Returns true if the snowflake holds an empty value (is 0) * * @return true if empty (zero) */ constexpr bool empty() const noexcept { return value == 0; } /** * @brief Returns the stringified version of the snowflake value * * @return std::string string form of snowflake value */ inline std::string str() const { return std::to_string(value); } /** * @brief Comparison operator with another snowflake * * @param snowflake_val snowflake */ constexpr bool operator==(dpp::snowflake snowflake_val) const noexcept { return value == snowflake_val.value; } /** * @brief Comparison operator with a string * * @param snowflake_val snowflake value as a string */ bool operator==(std::string_view snowflake_val) const noexcept; /** * @brief Comparison operator with an integer * * @param snowflake_val snowflake value as an integer type */ template >> constexpr bool operator==(T snowflake_val) const noexcept { /* We use the std::enable_if_t trick to disable implicit conversions so there is a perfect candidate for overload resolution for integers, and it isn't ambiguous */ return *this == dpp::snowflake{snowflake_val}; } /** * @brief For acting like an integer * @return The snowflake value */ constexpr operator uint64_t() const noexcept { return value; } /** * @brief For acting like an integer * @return A reference to the snowflake value */ constexpr operator uint64_t &() noexcept { return value; } /** * @brief For building json * @return The snowflake value as a string */ operator json() const; /** * @brief Get the creation time of this snowflake according to Discord. * * @return double creation time inferred from the snowflake ID. * The minimum possible value is the first second of 2015. */ constexpr double get_creation_time() const noexcept { constexpr uint64_t first_january_2016 = 1420070400000ull; return static_cast((value >> 22) + first_january_2016) / 1000.0; } /** * @brief Get the worker id that produced this snowflake value * * @return uint8_t worker id */ constexpr uint8_t get_worker_id() const noexcept { return static_cast((value & 0x3E0000) >> 17); } /** * @brief Get the process id that produced this snowflake value * * @return uint8_t process id */ constexpr uint8_t get_process_id() const noexcept { return static_cast((value & 0x1F000) >> 12); } /** * @brief Get the increment, which is incremented for every snowflake * created over the one millisecond resolution in the timestamp. * * @return uint64_t millisecond increment */ constexpr uint16_t get_increment() const noexcept { return static_cast(value & 0xFFF); } /** * @brief Helper function for libfmt so that a snowflake can be directly formatted as a uint64_t. * * @see https://fmt.dev/latest/api.html#formatting-user-defined-types * @return uint64_t snowflake ID */ friend constexpr uint64_t format_as(snowflake s) noexcept { /* note: this function must stay as "friend" - this declares it as a free function but makes it invisible unless the argument is snowflake * this effectively means no implicit conversions are performed to snowflake, for example format_as(0) doesn't call this function */ return s.value; } }; } // namespace dpp template<> struct std::hash { /** * @brief Hashing function for dpp::snowflake * Used by std::unordered_map. This just calls std::hash. * * @param s Snowflake value to hash * @return std::size_t hash value */ std::size_t operator()(dpp::snowflake s) const noexcept { return std::hash{}(s.value); } };