#pragma once #include "tl/detail/prologue.h" #include /////////////////////////////////////////////////////////////////////////////// namespace tl { namespace assert { //The assert handler is called whenever an assert fails and should present a message to the user. //If it returns BREAK, the code will dbgbreak and allow you to connect with the debugger. Returning CONTINUE will continue the code normally // condition contains a stringified condition that failed // msg is any user supplied message. Can be NULL // file is the name of the file the assert failed // line is the line where the assert failed enum class response { BREAK, CONTINUE }; using handler = response (*)(const char *, const char *, int, const char *); TL_API handler set_handler(handler) noexcept; TL_API response default_handler(const char* condition, const char* file, int line, const char* msg) noexcept; inline response null_handler(const char*, const char*, int, const char*) noexcept { return response::CONTINUE; } //Use this to temporarily change the assert handler for the current scope class handler_guard { public: explicit handler_guard(handler new_handler) noexcept { m_prev_handler = set_handler(new_handler); } ~handler_guard() noexcept { set_handler(m_prev_handler); } private: handler m_prev_handler; }; /////////////////////////////////////////////////////////////////////// //for internal use only namespace detail { //helper for checking if a description is used instead of an assert condition template struct _description_in_condition { static const bool value = false; }; template<> struct _description_in_condition { static const bool value = true; }; template struct _description_in_condition { static const bool value = true; }; template struct description_in_condition { static const bool value = _description_in_condition>>::value; }; //this is responsible of formatting the messages and calling the handler TL_API response do_assert(const char* _condition, const char* _file, int _line, const char* msg) noexcept; TL_API response do_assert(const char* _condition, const char* _file, int _line) noexcept; //used to mark/test once asserts as being taken or not TL_API bool is_assert_enabled(const char* _file, int _line) noexcept; TL_API void set_assert_enabled(const char* _file, int _line, bool _enabled) noexcept; inline const char* get_plain_message() noexcept { return ""; } inline const char* get_plain_message(const char* msg) noexcept { return msg; } TL_API handler& get_static_handler() noexcept; } } } #define __TL_ASSERT_TEST_DESCRIPTION_IN_CONDITION__(condition) \ const auto& __cond_result__ = (condition); \ (void)(__cond_result__); \ static_assert(tl::assert::detail::description_in_condition::value == false, \ "It seems you used the assert description instead of the assert condition.") //ASSERT #define __TL_ASSERT_ENABLED_IMPL__(condition, ...) \ do \ { \ __TL_ASSERT_TEST_DESCRIPTION_IN_CONDITION__(condition); \ if (!(__cond_result__)) [[unlikely]] \ { \ eastl::string __msg__ = tl::format(__VA_ARGS__); \ if (tl::assert::detail::do_assert(#condition, __FILE__, __LINE__, __msg__.data()) == tl::assert::response::BREAK) \ TL_BREAK(); \ } \ } while(false) #define __TL_ASSERT_ENABLED_IMPL_ONCE__(condition, ...) \ do \ { \ __TL_ASSERT_TEST_DESCRIPTION_IN_CONDITION__(condition); \ /* checking the condition first and then the GetAssertOnce, as chances are that the condition is cheaper */ \ if (!(__cond_result__) && tl::assert::detail::is_assert_enabled(__FILE__, __LINE__)) [[unlikely]] \ { \ tl::assert::detail::set_assert_enabled(__FILE__, __LINE__, false); \ eastl::string __msg__ = tl::format(__VA_ARGS__); \ if (tl::assert::detail::do_assert(#condition, __FILE__, __LINE__, __msg__.data()) == tl::assert::response::BREAK) \ TL_BREAK(); \ } \ } while(false) //PLAIN ASSERT #define __TL_PLAIN_ASSERT_ENABLED_IMPL__(condition, ...) \ do \ { \ __TL_ASSERT_TEST_DESCRIPTION_IN_CONDITION__(condition); \ if (!(__cond_result__)) [[unlikely]] \ { \ if (tl::assert::detail::do_assert(#condition, __FILE__, __LINE__, ##__VA_ARGS__) == tl::assert::response::BREAK) \ TL_BREAK(); \ } \ } while(false) #define __TL_PLAIN_ASSERT_ENABLED_IMPL_ONCE__(condition, ...) \ do \ { \ __TL_ASSERT_TEST_DESCRIPTION_IN_CONDITION__(condition); \ /* checking the condition first and then the GetAssertOnce, as chances are that the condition is cheaper */ \ if (!(__cond_result__) && tl::assert::detail::is_assert_enabled(__FILE__, __LINE__)) [[unlikely]] \ { \ tl::assert::detail::set_assert_enabled(__FILE__, __LINE__, false); \ if (tl::assert::detail::do_assert(#condition, __FILE__, __LINE__, ##__VA_ARGS__) == tl::assert::response::BREAK) \ TL_BREAK(); \ } \ } while(false) //FAIL #define __TL_FAIL_ENABLED_IMPL__(...) \ do \ { \ eastl::string __msg__ = tl::format(__VA_ARGS__); \ if (tl::assert::detail::do_assert("fail", __FILE__, __LINE__, __msg__.data()) == tl::assert::response::BREAK) \ TL_BREAK(); \ } while(false) #define __TL_FAIL_ENABLED_IMPL_ONCE__(...) \ do \ { \ if (tl::assert::detail::is_assert_enabled(__FILE__, __LINE__)) [[likely]] \ { \ tl::assert::detail::set_assert_enabled(__FILE__, __LINE__, false); \ eastl::string __msg__ = tl::format(__VA_ARGS__); \ if (tl::assert::detail::do_assert("fail", __FILE__, __LINE__, __msg__.data()) == tl::assert::response::BREAK) \ TL_BREAK(); \ } \ } while(false) //PLAIN FAIL #define __TL_PLAIN_FAIL_ENABLED_IMPL__(...) \ do \ { \ const char* __msg__ = tl::assert::detail::get_plain_message(__VA_ARGS__); \ if (tl::assert::detail::do_assert("fail", __FILE__, __LINE__, __msg__) == tl::assert::response::BREAK) \ TL_BREAK(); \ } while(false) #define __TL_PLAIN_FAIL_ENABLED_IMPL_ONCE__(...) \ do \ { \ if (tl::assert::detail::is_assert_enabled(__FILE__, __LINE__)) [[likely]] \ { \ tl::assert::detail::set_assert_enabled(__FILE__, __LINE__, false); \ const char* __msg__ = tl::assert::detail::get_plain_message(__VA_ARGS__); \ if (tl::assert::detail::do_assert("fail", __FILE__, __LINE__, __msg__) == tl::assert::response::BREAK) \ TL_BREAK(); \ } \ } while(false) #define __TL_ASSERT_DISABLED_IMPL__(condition, ...) do {} while (false) #define __TL_ASSERT_DISABLED_IMPL_ONCE__(condition, ...) do {} while (false) #define __TL_PLAIN_ASSERT_DISABLED_IMPL__(condition, ...) do {} while (false) #define __TL_PLAIN_ASSERT_DISABLED_IMPL_ONCE__(condition, ...) do {} while (false) #define __TL_FAIL_DISABLED_IMPL__(...) do {} while (false) #define __TL_FAIL_DISABLED_IMPL_ONCE__(...) do {} while (false) #define __TL_PLAIN_FAIL_DISABLED_IMPL__(...) do {} while (false) #define __TL_PLAIN_FAIL_DISABLED_IMPL_ONCE__(...) do {} while (false) #ifdef TL_DEBUG //assert #define TL_ASSERT __TL_ASSERT_ENABLED_IMPL__ #define TL_ASSERT_ONCE __TL_ASSERT_ENABLED_IMPL_ONCE__ #define TL_ASSERT_RELEASE __TL_ASSERT_ENABLED_IMPL__ #define TL_ASSERT_RELEASE_ONCE __TL_ASSERT_ENABLED_IMPL_ONCE__ //plain assert #define TL_PLAIN_ASSERT __TL_PLAIN_ASSERT_ENABLED_IMPL__ #define TL_PLAIN_ASSERT_ONCE __TL_PLAIN_ASSERT_ENABLED_IMPL_ONCE__ #define TL_PLAIN_ASSERT_RELEASE __TL_PLAIN_ASSERT_ENABLED_IMPL__ #define TL_PLAIN_ASSERT_RELEASE_ONCE __TL_PLAIN_ASSERT_ENABLED_IMPL_ONCE__ //fail #define TL_FAIL __TL_FAIL_ENABLED_IMPL__ #define TL_FAIL_ONCE __TL_FAIL_ENABLED_IMPL_ONCE__ #define TL_FAIL_RELEASE __TL_FAIL_ENABLED_IMPL__ #define TL_FAIL_RELEASE_ONCE __TL_FAIL_ENABLED_IMPL_ONCE__ //plain fail #define TL_PLAIN_FAIL __TL_PLAIN_FAIL_ENABLED_IMPL__ #define TL_PLAIN_FAIL_ONCE __TL_PLAIN_FAIL_ENABLED_IMPL_ONCE__ #define TL_PLAIN_FAIL_RELEASE __TL_PLAIN_FAIL_ENABLED_IMPL__ #define TL_PLAIN_FAIL_RELEASE_ONCE __TL_PLAIN_FAIL_ENABLED_IMPL_ONCE__ #else //assert #define TL_ASSERT __TL_ASSERT_DISABLED_IMPL__ #define TL_ASSERT_ONCE __TL_ASSERT_DISABLED_IMPL_ONCE__ #ifdef TL_RETAIL #define TL_ASSERT_RELEASE __TL_ASSERT_DISABLED_IMPL__ #define TL_ASSERT_RELEASE_ONCE __TL_ASSERT_DISABLED_IMPL_ONCE__ #define TL_PLAIN_ASSERT_RELEASE __TL_ASSERT_DISABLED_IMPL__ #define TL_PLAIN_ASSERT_RELEASE_ONCE __TL_ASSERT_DISABLED_IMPL_ONCE__ #define TL_FAIL_RELEASE __TL_ASSERT_DISABLED_IMPL__ #define TL_FAIL_RELEASE_ONCE __TL_ASSERT_DISABLED_IMPL_ONCE__ #define TL_PLAIN_FAIL_RELEASE __TL_ASSERT_DISABLED_IMPL__ #define TL_PLAIN_FAIL_RELEASE_ONCE __TL_ASSERT_DISABLED_IMPL_ONCE__ #else #define TL_ASSERT_RELEASE __TL_ASSERT_ENABLED_IMPL__ #define TL_ASSERT_RELEASE_ONCE __TL_ASSERT_ENABLED_IMPL_ONCE__ #define TL_PLAIN_ASSERT_RELEASE __TL_PLAIN_ASSERT_ENABLED_IMPL__ #define TL_PLAIN_ASSERT_RELEASE_ONCE __TL_PLAIN_ASSERT_ENABLED_IMPL_ONCE__ #define TL_FAIL_RELEASE __TL_FAIL_ENABLED_IMPL__ #define TL_FAIL_RELEASE_ONCE __TL_FAIL_ENABLED_IMPL_ONCE__ #define TL_PLAIN_FAIL_RELEASE __TL_PLAIN_FAIL_ENABLED_IMPL__ #define TL_PLAIN_FAIL_RELEASE_ONCE __TL_PLAIN_FAIL_ENABLED_IMPL_ONCE__ #endif //plain assert #define TL_PLAIN_ASSERT __TL_PLAIN_ASSERT_DISABLED_IMPL__ #define TL_PLAIN_ASSERT_ONCE __TL_PLAIN_ASSERT_DISABLED_IMPL_ONCE__ //fail #define TL_FAIL __TL_FAIL_DISABLED_IMPL__ #define TL_FAIL_ONCE __TL_FAIL_DISABLED_IMPL_ONCE__ //plain fail #define TL_PLAIN_FAIL __TL_PLAIN_FAIL_DISABLED_IMPL__ #define TL_PLAIN_FAIL_ONCE __TL_PLAIN_FAIL_DISABLED_IMPL_ONCE__ #endif