diff --git a/headers/enduro2d/core/debug.hpp b/headers/enduro2d/core/debug.hpp index f92cb4d8..d56f6825 100644 --- a/headers/enduro2d/core/debug.hpp +++ b/headers/enduro2d/core/debug.hpp @@ -18,7 +18,6 @@ namespace e2d error, fatal }; - class sink : private e2d::noncopyable { public: virtual ~sink() noexcept = default; @@ -27,37 +26,38 @@ namespace e2d using sink_uptr = std::unique_ptr; public: debug(); - ~debug(); + ~debug() noexcept; template < typename T, typename... Args > - T& add_sink(Args&&... args); - sink& add_sink(sink_uptr sink); + T& register_sink(Args&&... args); + sink& register_sink(sink_uptr sink); + void unregister_sink(const sink& sink); template < typename T, typename... Args > - T& add_sink_ex(level min_lvl, Args&&... args); - sink& add_sink_ex(level min_lvl, sink_uptr sink); + T& register_sink_ex(level min_lvl, Args&&... args); + sink& register_sink_ex(level min_lvl, sink_uptr sink); void set_min_level(level lvl) noexcept; level min_level() const noexcept; template < typename... Args > - debug& log(level lvl, str_view fmt, Args&&... args); + debug& log(level lvl, str_view fmt, Args&&... args) noexcept; template < typename... Args > - debug& trace(str_view fmt, Args&&... args); + debug& trace(str_view fmt, Args&&... args) noexcept; template < typename... Args > - debug& warning(str_view fmt, Args&&... args); + debug& warning(str_view fmt, Args&&... args) noexcept; template < typename... Args > - debug& error(str_view fmt, Args&&... args); + debug& error(str_view fmt, Args&&... args) noexcept; template < typename... Args > - debug& fatal(str_view fmt, Args&&... args); + debug& fatal(str_view fmt, Args&&... args) noexcept; private: mutable std::mutex mutex_; - level min_level_ = level::trace; vector> sinks_; + level min_level_ = level::trace; }; class debug_file_sink final : public debug::sink { @@ -77,36 +77,36 @@ namespace e2d namespace e2d { template < typename T, typename... Args > - T& debug::add_sink(Args&&... args) { - return add_sink_ex(level::trace, std::forward(args)...); - } - - inline debug::sink& debug::add_sink(sink_uptr sink) { - return add_sink_ex(level::trace, std::move(sink)); + T& debug::register_sink(Args&&... args) { + return register_sink_ex( + level::trace, + std::forward(args)...); } template < typename T, typename... Args > - T& debug::add_sink_ex(level min_lvl, Args&&... args) { - return static_cast(add_sink_ex( + T& debug::register_sink_ex(level min_lvl, Args&&... args) { + return static_cast(register_sink_ex( min_lvl, std::make_unique(std::forward(args)...))); } - inline debug::sink& debug::add_sink_ex(level min_lvl, sink_uptr sink) { - E2D_ASSERT(sink); - std::lock_guard guard(mutex_); - sinks_.push_back(std::make_pair(min_lvl, std::move(sink))); - return *sinks_.back().second; - } - template < typename... Args > - debug& debug::log(level lvl, str_view fmt, Args&&... args) { + debug& debug::log(level lvl, str_view fmt, Args&&... args) noexcept { std::lock_guard guard(mutex_); if ( lvl >= min_level_ && !sinks_.empty() ) { - str text = strings::rformat(fmt, std::forward(args)...); + str formatted_text; + try { + formatted_text = strings::rformat( + fmt, std::forward(args)...); + } catch (...) { + E2D_ASSERT_MSG(false, "DEBUG: ignored log formatting exception"); + return *this; + } for ( const auto& pair : sinks_ ) { if ( lvl >= pair.first && pair.second ) { - pair.second->on_message(lvl, text); + bool success = pair.second->on_message(lvl, formatted_text); + E2D_UNUSED(success); + E2D_ASSERT_MSG(success, "DEBUG: ignored failed log sink call"); } } } @@ -114,22 +114,22 @@ namespace e2d } template < typename... Args > - debug& debug::trace(str_view fmt, Args&&... args) { + debug& debug::trace(str_view fmt, Args&&... args) noexcept { return log(level::trace, fmt, std::forward(args)...); } template < typename... Args > - debug& debug::warning(str_view fmt, Args&&... args) { + debug& debug::warning(str_view fmt, Args&&... args) noexcept { return log(level::warning, fmt, std::forward(args)...); } template < typename... Args > - debug& debug::error(str_view fmt, Args&&... args) { + debug& debug::error(str_view fmt, Args&&... args) noexcept { return log(level::error, fmt, std::forward(args)...); } template < typename... Args > - debug& debug::fatal(str_view fmt, Args&&... args) { + debug& debug::fatal(str_view fmt, Args&&... args) noexcept { return log(level::fatal, fmt, std::forward(args)...); } } diff --git a/headers/enduro2d/utils/strfmts.hpp b/headers/enduro2d/utils/strfmts.hpp index bad15136..63c57c7e 100644 --- a/headers/enduro2d/utils/strfmts.hpp +++ b/headers/enduro2d/utils/strfmts.hpp @@ -341,23 +341,25 @@ namespace e2d { namespace strings template <> class format_arg { str value_; + u8 width_; public: template < typename U > - explicit format_arg(U&& value) + explicit format_arg(U&& value, u8 width = 0) noexcept(noexcept(std::is_nothrow_constructible::value)) - : value_(std::forward(value)) {} + : value_(std::forward(value)), width_(width) {} std::ptrdiff_t write(char* dst, size_t size) const { return math::numeric_cast( - format(dst, size, "%0", value_.c_str())); + format(dst, size, "%0", + make_format_arg(value_.c_str(), width_))); } }; template <> class format_arg : public format_arg { public: - explicit format_arg(str_view sv) - : format_arg(sv) {} + explicit format_arg(str_view sv, u8 width = 0) + : format_arg(sv, width) {} }; // @@ -367,23 +369,25 @@ namespace e2d { namespace strings template <> class format_arg { wstr value_; + u8 width_; public: template < typename U > - explicit format_arg(U&& value) + explicit format_arg(U&& value, u8 width = 0) noexcept(noexcept(std::is_nothrow_constructible::value)) - : value_(std::forward(value)) {} + : value_(std::forward(value)), width_(width) {} std::ptrdiff_t write(char* dst, size_t size) const { return math::numeric_cast( - format(dst, size, "%0", make_utf8(value_.c_str()))); + format(dst, size, "%0", + make_format_arg(make_utf8(value_), width_))); } }; template <> class format_arg : public format_arg { public: - explicit format_arg(wstr_view sv) - : format_arg(sv) {} + explicit format_arg(wstr_view sv, u8 width = 0) + : format_arg(sv, width) {} }; // @@ -393,23 +397,25 @@ namespace e2d { namespace strings template <> class format_arg { str16 value_; + u8 width_; public: template < typename U > - explicit format_arg(U&& value) + explicit format_arg(U&& value, u8 width = 0) noexcept(noexcept(std::is_nothrow_constructible::value)) - : value_(std::forward(value)) {} + : value_(std::forward(value)), width_(width) {} std::ptrdiff_t write(char* dst, size_t size) const { return math::numeric_cast( - format(dst, size, "%0", make_utf8(value_.c_str()))); + format(dst, size, "%0", + make_format_arg(make_utf8(value_), width_))); } }; template <> class format_arg : public format_arg { public: - explicit format_arg(str16_view sv) - : format_arg(sv) {} + explicit format_arg(str16_view sv, u8 width = 0) + : format_arg(sv, width) {} }; // @@ -419,23 +425,25 @@ namespace e2d { namespace strings template <> class format_arg { str32 value_; + u8 width_; public: template < typename U > - explicit format_arg(U&& value) + explicit format_arg(U&& value, u8 width = 0) noexcept(noexcept(std::is_nothrow_constructible::value)) - : value_(std::forward(value)) {} + : value_(std::forward(value)), width_(width) {} std::ptrdiff_t write(char* dst, size_t size) const { return math::numeric_cast( - format(dst, size, "%0", make_utf8(value_.c_str()))); + format(dst, size, "%0", + make_format_arg(make_utf8(value_), width_))); } }; template <> class format_arg : public format_arg { public: - explicit format_arg(str32_view sv) - : format_arg(sv) {} + explicit format_arg(str32_view sv, u8 width = 0) + : format_arg(sv, width) {} }; // diff --git a/headers/enduro2d/utils/strings.inl b/headers/enduro2d/utils/strings.inl index 2acc2276..29e43f38 100644 --- a/headers/enduro2d/utils/strings.inl +++ b/headers/enduro2d/utils/strings.inl @@ -146,7 +146,7 @@ namespace e2d { namespace strings : value_(value), width_(width), precision_(precision) {} std::ptrdiff_t write(char* dst, size_t size) const noexcept { - char format[10] = {0}; + char format[11] = {0}; char* b_format = format; *b_format++ = '%'; b_format = impl::u8toa(width_, b_format); @@ -178,24 +178,40 @@ namespace e2d { namespace strings template <> class format_arg { const char* value_; + u8 width_; public: - explicit format_arg(const char* value) noexcept - : value_(value) {} + explicit format_arg(const char* value, u8 width = 0) noexcept + : value_(value), width_(width) {} std::ptrdiff_t write(char* dst, size_t size) const noexcept { - return std::snprintf(dst, size, "%s", value_); + char format[6] = {0}; + char* b_format = format; + *b_format++ = '%'; + b_format = impl::u8toa(width_, b_format); + *b_format++ = 's'; + E2D_ASSERT(b_format < format + sizeof(format)); + return std::snprintf( + dst, size, format, value_); } }; template <> class format_arg { const char* value_; + u8 width_; public: - explicit format_arg(const char* value) noexcept - : value_(value) {} + explicit format_arg(const char* value, u8 width = 0) noexcept + : value_(value), width_(width) {} std::ptrdiff_t write(char* dst, size_t size) const noexcept { - return std::snprintf(dst, size, "%s", value_); + char format[6] = {0}; + char* b_format = format; + *b_format++ = '%'; + b_format = impl::u8toa(width_, b_format); + *b_format++ = 's'; + E2D_ASSERT(b_format < format + sizeof(format)); + return std::snprintf( + dst, size, format, value_); } }; diff --git a/samples/sources/sample_00/sample_00.cpp b/samples/sources/sample_00/sample_00.cpp index c7b6562f..5860cde1 100644 --- a/samples/sources/sample_00/sample_00.cpp +++ b/samples/sources/sample_00/sample_00.cpp @@ -13,7 +13,7 @@ int e2d_main() { modules::initialize(the()); modules::initialize(v2u{640, 480}, "Enduro2D", false); - the().add_sink(); + the().register_sink(); the().register_event_listener(the()); const keyboard& k = the().keyboard(); diff --git a/samples/sources/sample_01/sample_01.cpp b/samples/sources/sample_01/sample_01.cpp index c5692b86..3ba3e18e 100644 --- a/samples/sources/sample_01/sample_01.cpp +++ b/samples/sources/sample_01/sample_01.cpp @@ -12,7 +12,7 @@ int e2d_main() { modules::initialize(); modules::initialize(v2u{640, 480}, "Enduro2D", false); - the().add_sink(); + the().register_sink(); the().register_event_listener(the()); the().register_event_listener(the()); diff --git a/sources/enduro2d/core/debug.cpp b/sources/enduro2d/core/debug.cpp index cbb7b072..5ea74a2f 100644 --- a/sources/enduro2d/core/debug.cpp +++ b/sources/enduro2d/core/debug.cpp @@ -10,35 +10,50 @@ namespace { using namespace e2d; - const char* level_to_str(debug::level lvl) noexcept { - switch ( lvl ) { - case debug::level::trace: return "trace"; - case debug::level::warning: return "warning"; - case debug::level::error: return "error"; - case debug::level::fatal: return "fatal"; + const char* level_to_cstr(debug::level l) noexcept { + #define DEFINE_CASE(x) case debug::level::x: return #x + switch ( l ) { + DEFINE_CASE(trace); + DEFINE_CASE(warning); + DEFINE_CASE(error); + DEFINE_CASE(fatal); default: - E2D_ASSERT(false); + E2D_ASSERT_MSG(false, "unexpected level"); return "unknown"; } + #undef DEFINE_CASE } str log_text_format(debug::level lvl, str_view text) { return strings::rformat( "[%0](%1) -> %2\n", - level_to_str(lvl), time::now_ms(), text); + level_to_cstr(lvl), time::now_ms(), text); } } namespace e2d { - // - // debug - // + debug::debug() = default; + debug::~debug() noexcept = default; - debug::debug() { + debug::sink& debug::register_sink(sink_uptr sink) { + return register_sink_ex(level::trace, std::move(sink)); } - debug::~debug() { + void debug::unregister_sink(const sink& sink) { + std::lock_guard guard(mutex_); + sinks_.erase(std::remove_if( + sinks_.begin(), sinks_.end(), + [&sink](const std::pair& p){ + return p.second.get() == &sink; + }), sinks_.end()); + } + + debug::sink& debug::register_sink_ex(level min_lvl, sink_uptr sink) { + E2D_ASSERT(sink); + std::lock_guard guard(mutex_); + sinks_.push_back(std::make_pair(min_lvl, std::move(sink))); + return *sinks_.back().second; } void debug::set_min_level(level lvl) noexcept { @@ -57,15 +72,16 @@ namespace e2d debug_file_sink::debug_file_sink(str_view path) : path_(path) { - make_write_file(path, false); + const auto file = make_write_file(path, false); + E2D_UNUSED(file); + E2D_ASSERT_MSG(file, "DEBUG: ignored failed sink file cleaning"); } bool debug_file_sink::on_message(debug::level lvl, str_view text) noexcept { try { - auto file = make_write_file(path_, true); - str log_text = log_text_format(lvl, text); + const auto file = make_write_file(path_, true); return file && output_sequence(*file) - .write(log_text.c_str(), log_text.length()) + .write_all(log_text_format(lvl, text)) .success(); } catch (...) { return false; @@ -78,7 +94,7 @@ namespace e2d bool debug_console_sink::on_message(debug::level lvl, str_view text) noexcept { try { - str log_text = log_text_format(lvl, text); + const str log_text = log_text_format(lvl, text); const std::ptrdiff_t rprintf = std::printf("%s", log_text.c_str()); return rprintf >= 0 && math::numeric_cast(rprintf) == log_text.length(); diff --git a/untests/sources/untests_core/debug.cpp b/untests/sources/untests_core/debug.cpp index 83740f9c..916283ea 100644 --- a/untests/sources/untests_core/debug.cpp +++ b/untests/sources/untests_core/debug.cpp @@ -14,36 +14,42 @@ namespace class test_sink final : public debug::sink { public: str on_message_acc; + static str s_on_message_acc; bool on_message(debug::level lvl, str_view text) noexcept final { E2D_UNUSED(lvl); on_message_acc.append(text.cbegin(), text.cend()); + s_on_message_acc.append(text.cbegin(), text.cend()); return true; } }; + str test_sink::s_on_message_acc; } TEST_CASE("debug"){ { debug d; - test_sink& s = d.add_sink(); - REQUIRE(s.on_message_acc.empty()); + test_sink& s = d.register_sink(); + REQUIRE(test_sink::s_on_message_acc.empty()); d.trace("h"); d.warning("e"); - REQUIRE(s.on_message_acc == "he"); + REQUIRE(test_sink::s_on_message_acc == "he"); d.set_min_level(debug::level::error); d.trace("el"); d.warning("lo"); - REQUIRE(s.on_message_acc == "he"); + REQUIRE(test_sink::s_on_message_acc == "he"); d.error("ll"); - REQUIRE(s.on_message_acc == "hell"); + REQUIRE(test_sink::s_on_message_acc == "hell"); d.fatal("o"); - REQUIRE(s.on_message_acc == "hello"); + REQUIRE(test_sink::s_on_message_acc == "hello"); + d.unregister_sink(s); + d.fatal("!!!"); + REQUIRE(test_sink::s_on_message_acc == "hello"); } { modules::initialize(); - test_sink& s1 = the().add_sink_ex(debug::level::warning); - test_sink& s2 = the().add_sink_ex(debug::level::error); + test_sink& s1 = the().register_sink_ex(debug::level::warning); + test_sink& s2 = the().register_sink_ex(debug::level::error); REQUIRE(s1.on_message_acc.empty()); REQUIRE(s2.on_message_acc.empty()); the().trace("w"); diff --git a/untests/sources/untests_utils/strings.cpp b/untests/sources/untests_utils/strings.cpp index ecda5a06..7a7e0c66 100644 --- a/untests/sources/untests_utils/strings.cpp +++ b/untests/sources/untests_utils/strings.cpp @@ -344,6 +344,36 @@ TEST_CASE("strings") { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9) == str("0 2 1 4 3 6 7 5 8 9")); } + { + REQUIRE(strings::rformat("%0", strings::make_format_arg("ab", u8(4))) == " ab"); + REQUIRE(strings::rformat("%0", strings::make_format_arg("ab", u8(3))) == " ab"); + REQUIRE(strings::rformat("%0", strings::make_format_arg("ab", u8(2))) == "ab"); + REQUIRE(strings::rformat("%0", strings::make_format_arg("ab", u8(1))) == "ab"); + REQUIRE(strings::rformat("%0", strings::make_format_arg("ab", u8(0))) == "ab"); + + REQUIRE(strings::rformat("%0", strings::make_format_arg(str("ab"), u8(4))) == " ab"); + REQUIRE(strings::rformat("%0", strings::make_format_arg(str("ab"), u8(3))) == " ab"); + REQUIRE(strings::rformat("%0", strings::make_format_arg(str("ab"), u8(2))) == "ab"); + REQUIRE(strings::rformat("%0", strings::make_format_arg(str("ab"), u8(1))) == "ab"); + REQUIRE(strings::rformat("%0", strings::make_format_arg(str("ab"), u8(0))) == "ab"); + + { + auto s1 = make_utf8("ab"); + auto s2 = make_wide("ab"); + auto s3 = make_utf16("ab"); + auto s4 = make_utf32("ab"); + + REQUIRE(strings::rformat("%0", strings::make_format_arg(s1, u8(4))) == " ab"); + REQUIRE(strings::rformat("%0", strings::make_format_arg(s2, u8(4))) == " ab"); + REQUIRE(strings::rformat("%0", strings::make_format_arg(s3, u8(4))) == " ab"); + REQUIRE(strings::rformat("%0", strings::make_format_arg(s4, u8(4))) == " ab"); + + REQUIRE(strings::rformat("%0", strings::make_format_arg(str_view(s1), u8(4))) == " ab"); + REQUIRE(strings::rformat("%0", strings::make_format_arg(wstr_view(s2), u8(4))) == " ab"); + REQUIRE(strings::rformat("%0", strings::make_format_arg(str16_view(s3), u8(4))) == " ab"); + REQUIRE(strings::rformat("%0", strings::make_format_arg(str32_view(s4), u8(4))) == " ab"); + } + } { REQUIRE(strings::rformat("%0", strings::make_format_arg(-5, u8(3))) == " -5"); REQUIRE(strings::rformat("%0", strings::make_format_arg(-5, u8(4))) == " -5");