From 8caca3d7d23f00b51880e7ca4c16ab9221535300 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Tue, 2 Oct 2018 17:52:16 +0700 Subject: [PATCH 1/5] rename debug add_sink to register_sink. add unregister_sink. --- headers/enduro2d/core/debug.hpp | 33 +++++++------------- samples/sources/sample_00/sample_00.cpp | 2 +- samples/sources/sample_01/sample_01.cpp | 2 +- sources/enduro2d/core/debug.cpp | 41 +++++++++++++++++-------- untests/sources/untests_core/debug.cpp | 9 ++++-- 5 files changed, 47 insertions(+), 40 deletions(-) diff --git a/headers/enduro2d/core/debug.hpp b/headers/enduro2d/core/debug.hpp index f92cb4d8..5bb5c035 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,15 +26,16 @@ 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; @@ -56,8 +56,8 @@ namespace e2d debug& fatal(str_view fmt, Args&&... args); 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,28 +77,17 @@ 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) { std::lock_guard guard(mutex_); diff --git a/samples/sources/sample_00/sample_00.cpp b/samples/sources/sample_00/sample_00.cpp index 52e7c92a..f8afce9f 100644 --- a/samples/sources/sample_00/sample_00.cpp +++ b/samples/sources/sample_00/sample_00.cpp @@ -13,7 +13,7 @@ int e2d_main() { window& w = modules::initialize( v2u{640, 480}, "Enduro2D", true, false); - d.add_sink(); + d.register_sink(); w.register_event_listener(i); w.register_event_listener(d); diff --git a/samples/sources/sample_01/sample_01.cpp b/samples/sources/sample_01/sample_01.cpp index 52e7c92a..f8afce9f 100644 --- a/samples/sources/sample_01/sample_01.cpp +++ b/samples/sources/sample_01/sample_01.cpp @@ -13,7 +13,7 @@ int e2d_main() { window& w = modules::initialize( v2u{640, 480}, "Enduro2D", true, false); - d.add_sink(); + d.register_sink(); w.register_event_listener(i); w.register_event_listener(d); diff --git a/sources/enduro2d/core/debug.cpp b/sources/enduro2d/core/debug.cpp index cbb7b072..02866329 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 { diff --git a/untests/sources/untests_core/debug.cpp b/untests/sources/untests_core/debug.cpp index 83740f9c..75c6601a 100644 --- a/untests/sources/untests_core/debug.cpp +++ b/untests/sources/untests_core/debug.cpp @@ -26,7 +26,7 @@ namespace TEST_CASE("debug"){ { debug d; - test_sink& s = d.add_sink(); + test_sink& s = d.register_sink(); REQUIRE(s.on_message_acc.empty()); d.trace("h"); d.warning("e"); @@ -39,11 +39,14 @@ TEST_CASE("debug"){ REQUIRE(s.on_message_acc == "hell"); d.fatal("o"); REQUIRE(s.on_message_acc == "hello"); + d.unregister_sink(s); + d.fatal("!!!"); + REQUIRE(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"); From 93f49f90fdd3820c01b05120c482d728e5643b67 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Sat, 29 Sep 2018 23:53:48 +0700 Subject: [PATCH 2/5] string operations for streams --- headers/enduro2d/utils/streams.hpp | 92 ++++++---------- sources/enduro2d/utils/streams.cpp | 168 +++++++++++++++++++++++++---- 2 files changed, 181 insertions(+), 79 deletions(-) diff --git a/headers/enduro2d/utils/streams.hpp b/headers/enduro2d/utils/streams.hpp index 47a0ad9a..97dccdf6 100644 --- a/headers/enduro2d/utils/streams.hpp +++ b/headers/enduro2d/utils/streams.hpp @@ -39,45 +39,27 @@ namespace e2d namespace e2d { - class input_sequence : private noncopyable { + class input_sequence final : private noncopyable { public: - input_sequence(input_stream& stream) noexcept - : stream_(stream) {} + input_sequence(input_stream& stream) noexcept; + ~input_sequence() noexcept = default; - bool success() const noexcept { - return success_; - } + bool success() const noexcept; + std::exception_ptr exception() const noexcept; - std::exception_ptr exception() const noexcept { - return exception_; - } - - input_sequence& seek(std::ptrdiff_t offset, bool relative) noexcept { - try { - stream_.seek(offset, relative); - } catch (...) { - success_ = false; - exception_ = std::current_exception(); - } - return *this; - } + input_sequence& seek(std::ptrdiff_t offset, bool relative) noexcept; + input_sequence& read(void* dst, std::size_t size) noexcept; + input_sequence& read_all(str& dst) noexcept; + input_sequence& read_all(buffer& dst) noexcept; template < typename T > std::enable_if_t< std::is_arithmetic::value, input_sequence&> read(T& v) noexcept { - return read(&v, sizeof(v)); - } - - input_sequence& read(void* dst, std::size_t size) noexcept { - try { - success_ = success_ && stream_.read(dst, size) == size; - } catch (...) { - success_ = false; - exception_ = std::current_exception(); - } - return *this; + return success_ + ? read(&v, sizeof(v)) + : *this; } private: input_stream& stream_; @@ -85,45 +67,27 @@ namespace e2d std::exception_ptr exception_ = nullptr; }; - class output_sequence : private noncopyable { + class output_sequence final : private noncopyable { public: - output_sequence(output_stream& stream) noexcept - : stream_(stream) {} + output_sequence(output_stream& stream) noexcept; + ~output_sequence() noexcept = default; - bool success() const noexcept { - return success_; - } + bool success() const noexcept; + std::exception_ptr exception() const noexcept; - std::exception_ptr exception() const noexcept { - return exception_; - } - - output_sequence& seek(std::ptrdiff_t offset, bool relative) noexcept { - try { - stream_.seek(offset, relative); - } catch (...) { - success_ = false; - exception_ = std::current_exception(); - } - return *this; - } + output_sequence& seek(std::ptrdiff_t offset, bool relative) noexcept; + output_sequence& write(const void* src, std::size_t size) noexcept; + output_sequence& write_all(const str& src) noexcept; + output_sequence& write_all(const buffer& src) noexcept; template < typename T > std::enable_if_t< std::is_arithmetic::value, output_sequence&> write(T v) noexcept { - return write(&v, sizeof(v)); - } - - output_sequence& write(const void* src, std::size_t size) noexcept { - try { - success_ = success_ && stream_.write(src, size) == size; - } catch (...) { - success_ = false; - exception_ = std::current_exception(); - } - return *this; + return success_ + ? write(&v, sizeof(v)) + : *this; } private: output_stream& stream_; @@ -139,10 +103,18 @@ namespace e2d namespace e2d { namespace streams { + bool try_read_tail( + str& dst, + const input_stream_uptr& stream) noexcept; + bool try_read_tail( buffer& dst, const input_stream_uptr& stream) noexcept; + bool try_write_tail( + const str& src, + const output_stream_uptr& stream) noexcept; + bool try_write_tail( const buffer& src, const output_stream_uptr& stream) noexcept; diff --git a/sources/enduro2d/utils/streams.cpp b/sources/enduro2d/utils/streams.cpp index b09715a5..839ef4ae 100644 --- a/sources/enduro2d/utils/streams.cpp +++ b/sources/enduro2d/utils/streams.cpp @@ -63,6 +63,129 @@ namespace }; } +namespace e2d +{ + // + // input_sequence + // + + input_sequence::input_sequence(input_stream& stream) noexcept + : stream_(stream) {} + + bool input_sequence::success() const noexcept { + return success_; + } + + std::exception_ptr input_sequence::exception() const noexcept { + return exception_; + } + + input_sequence& input_sequence::seek(std::ptrdiff_t offset, bool relative) noexcept { + try { + if ( success_ ) { + stream_.seek(offset, relative); + } + } catch (...) { + success_ = false; + exception_ = std::current_exception(); + } + return *this; + } + + input_sequence& input_sequence::read(void* dst, std::size_t size) noexcept { + try { + success_ = success_ && stream_.read(dst, size) == size; + } catch (...) { + success_ = false; + exception_ = std::current_exception(); + } + return *this; + } + + input_sequence& input_sequence::read_all(str& dst) noexcept { + try { + if ( success_ ) { + vector tail(stream_.length() - stream_.tell()); + if ( tail.size() == stream_.read(tail.data(), tail.size()) ) { + dst.assign(tail.data(), tail.data() + tail.size()); + } else { + success_ = false; + } + } + } catch (...) { + success_ = false; + exception_ = std::current_exception(); + } + return *this; + } + + input_sequence& input_sequence::read_all(buffer& dst) noexcept { + try { + if ( success_ ) { + buffer tail(stream_.length() - stream_.tell()); + if ( tail.size() == stream_.read(tail.data(), tail.size()) ) { + tail.swap(dst); + } else { + success_ = false; + } + } + } catch (...) { + success_ = false; + exception_ = std::current_exception(); + } + return *this; + } + + // + // output_sequence + // + + output_sequence::output_sequence(output_stream& stream) noexcept + : stream_(stream) {} + + bool output_sequence::success() const noexcept { + return success_; + } + + std::exception_ptr output_sequence::exception() const noexcept { + return exception_; + } + + output_sequence& output_sequence::seek(std::ptrdiff_t offset, bool relative) noexcept { + try { + if ( success_ ) { + stream_.seek(offset, relative); + } + } catch (...) { + success_ = false; + exception_ = std::current_exception(); + } + return *this; + } + + output_sequence& output_sequence::write(const void* src, std::size_t size) noexcept { + try { + success_ = success_ && stream_.write(src, size) == size; + } catch (...) { + success_ = false; + exception_ = std::current_exception(); + } + return *this; + } + + output_sequence& output_sequence::write_all(const buffer& src) noexcept { + return success_ + ? write(src.data(), src.size()) + : *this; + } + + output_sequence& output_sequence::write_all(const str& src) noexcept { + return success_ + ? write(src.c_str(), src.size()) + : *this; + } +} + namespace e2d { input_stream_uptr make_memory_stream(buffer data) noexcept { @@ -76,28 +199,35 @@ namespace e2d namespace e2d { namespace streams { + bool try_read_tail(str& dst, const input_stream_uptr& stream) noexcept { + return stream + ? input_sequence(*stream) + .read_all(dst) + .success() + : false; + } + bool try_read_tail(buffer& dst, const input_stream_uptr& stream) noexcept { - try { - if ( stream ) { - buffer tail(stream->length() - stream->tell()); - if ( tail.size() == stream->read(tail.data(), tail.size()) ) { - dst.swap(tail); - return true; - } - } - } catch (...) { - // nothing - } - return false; + return stream + ? input_sequence(*stream) + .read_all(dst) + .success() + : false; + } + + bool try_write_tail(const str& src, const output_stream_uptr& stream) noexcept { + return stream + ? output_sequence(*stream) + .write_all(src) + .success() + : false; } bool try_write_tail(const buffer& src, const output_stream_uptr& stream) noexcept { - try { - return - stream && - src.size() == stream->write(src.data(), src.size()); - } catch (...) { - return false; - } + return stream + ? output_sequence(*stream) + .write_all(src) + .success() + : false; } }} From 88068f8de3a1d17e346b6f0559b6e0723744af2a Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Tue, 2 Oct 2018 19:13:05 +0700 Subject: [PATCH 3/5] debug nothrow log calls --- headers/enduro2d/core/debug.hpp | 37 +++++++++++++++++++++------------ sources/enduro2d/core/debug.cpp | 11 +++++----- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/headers/enduro2d/core/debug.hpp b/headers/enduro2d/core/debug.hpp index 5bb5c035..d56f6825 100644 --- a/headers/enduro2d/core/debug.hpp +++ b/headers/enduro2d/core/debug.hpp @@ -41,19 +41,19 @@ namespace e2d 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_; vector> sinks_; @@ -78,7 +78,9 @@ namespace e2d { template < typename T, typename... Args > T& debug::register_sink(Args&&... args) { - return register_sink_ex(level::trace, std::forward(args)...); + return register_sink_ex( + level::trace, + std::forward(args)...); } template < typename T, typename... Args > @@ -89,13 +91,22 @@ namespace e2d } 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"); } } } @@ -103,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/sources/enduro2d/core/debug.cpp b/sources/enduro2d/core/debug.cpp index 02866329..5ea74a2f 100644 --- a/sources/enduro2d/core/debug.cpp +++ b/sources/enduro2d/core/debug.cpp @@ -72,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; @@ -93,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(); From c079e89f9939ecd999720cbde11328446f4118b0 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Tue, 2 Oct 2018 20:43:16 +0700 Subject: [PATCH 4/5] fix ci tests --- untests/sources/untests_core/debug.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/untests/sources/untests_core/debug.cpp b/untests/sources/untests_core/debug.cpp index 75c6601a..916283ea 100644 --- a/untests/sources/untests_core/debug.cpp +++ b/untests/sources/untests_core/debug.cpp @@ -14,34 +14,37 @@ 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.register_sink(); - REQUIRE(s.on_message_acc.empty()); + 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(s.on_message_acc == "hello"); + REQUIRE(test_sink::s_on_message_acc == "hello"); } { modules::initialize(); From 382fb10f002597fe4774665518a5dd8117b813ba Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Tue, 2 Oct 2018 22:40:43 +0700 Subject: [PATCH 5/5] width format parameter for string types --- headers/enduro2d/utils/strfmts.hpp | 48 +++++++++++++---------- headers/enduro2d/utils/strings.inl | 30 ++++++++++---- untests/sources/untests_utils/strings.cpp | 30 ++++++++++++++ 3 files changed, 81 insertions(+), 27 deletions(-) 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/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");