From 2ef4dd23aa3e40a8c8e19244c87d84a678cbac06 Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 30 Dec 2020 15:14:27 +0200 Subject: [PATCH 1/3] Replace std::stringstream with custom string buffer --- src/openrct2/localisation/Formatting.cpp | 52 ++++----- src/openrct2/localisation/Formatting.h | 127 +++++++++++++++++++-- test/tests/FormattingTests.cpp | 139 ++++++++++++----------- 3 files changed, 217 insertions(+), 101 deletions(-) diff --git a/src/openrct2/localisation/Formatting.cpp b/src/openrct2/localisation/Formatting.cpp index 03264639eb..30a2e5e355 100644 --- a/src/openrct2/localisation/Formatting.cpp +++ b/src/openrct2/localisation/Formatting.cpp @@ -19,7 +19,7 @@ namespace OpenRCT2 { - static void FormatMonthYear(std::stringstream& ss, int32_t month, int32_t year); + static void FormatMonthYear(FormatBuffer& ss, int32_t month, int32_t year); static std::optional ParseNumericToken(std::string_view s) { @@ -278,7 +278,7 @@ namespace OpenRCT2 return sz != nullptr ? sz : std::string_view(); } - void FormatRealName(std::stringstream& ss, rct_string_id id) + void FormatRealName(FormatBuffer& ss, rct_string_id id) { if (IsRealNameStringId(id)) { @@ -301,7 +301,7 @@ namespace OpenRCT2 } } - template void FormatNumber(std::stringstream& ss, T value) + template void FormatNumber(FormatBuffer& ss, T value) { char buffer[32]; size_t i = 0; @@ -379,7 +379,7 @@ namespace OpenRCT2 } } - template void FormatCurrency(std::stringstream& ss, T rawValue) + template void FormatCurrency(FormatBuffer& ss, T rawValue) { auto currencyDesc = &CurrencyDescriptors[EnumValue(gConfigGeneral.currency_format)]; auto value = static_cast(rawValue) * currencyDesc->rate; @@ -434,7 +434,7 @@ namespace OpenRCT2 } } - template static void FormatMinutesSeconds(std::stringstream& ss, T value) + template static void FormatMinutesSeconds(FormatBuffer& ss, T value) { static constexpr const rct_string_id Formats[][2] = { { STR_DURATION_SEC, STR_DURATION_SECS }, @@ -456,7 +456,7 @@ namespace OpenRCT2 } } - template static void FormatHoursMinutes(std::stringstream& ss, T value) + template static void FormatHoursMinutes(FormatBuffer& ss, T value) { static constexpr const rct_string_id Formats[][2] = { { STR_REALTIME_MIN, STR_REALTIME_MINS }, @@ -478,7 +478,7 @@ namespace OpenRCT2 } } - template void FormatArgument(std::stringstream& ss, FormatToken token, T arg) + template void FormatArgument(FormatBuffer& ss, FormatToken token, T arg) { switch (token) { @@ -625,12 +625,12 @@ namespace OpenRCT2 } } - template void FormatArgument(std::stringstream&, FormatToken, uint16_t); - template void FormatArgument(std::stringstream&, FormatToken, int16_t); - template void FormatArgument(std::stringstream&, FormatToken, int32_t); - template void FormatArgument(std::stringstream&, FormatToken, int64_t); - template void FormatArgument(std::stringstream&, FormatToken, uint64_t); - template void FormatArgument(std::stringstream&, FormatToken, const char*); + template void FormatArgument(FormatBuffer&, FormatToken, uint16_t); + template void FormatArgument(FormatBuffer&, FormatToken, int16_t); + template void FormatArgument(FormatBuffer&, FormatToken, int32_t); + template void FormatArgument(FormatBuffer&, FormatToken, int64_t); + template void FormatArgument(FormatBuffer&, FormatToken, uint64_t); + template void FormatArgument(FormatBuffer&, FormatToken, const char*); bool IsRealNameStringId(rct_string_id id) { @@ -643,27 +643,24 @@ namespace OpenRCT2 return FmtString(fmtc); } - std::stringstream& GetThreadFormatStream() + FormatBuffer& GetThreadFormatStream() { - thread_local std::stringstream ss; - // Reset the buffer (reported as most efficient way) - std::stringstream().swap(ss); + thread_local FormatBuffer ss; + ss.clear(); return ss; } - size_t CopyStringStreamToBuffer(char* buffer, size_t bufferLen, std::stringstream& ss) + size_t CopyStringStreamToBuffer(char* buffer, size_t bufferLen, FormatBuffer& ss) { - auto stringLen = ss.tellp(); - auto copyLen = std::min(bufferLen - 1, stringLen); + auto copyLen = std::min(bufferLen - 1, ss.size()); - ss.seekg(0, std::ios::beg); - ss.read(buffer, copyLen); + std::copy(ss.data(), ss.data() + copyLen, buffer); buffer[copyLen] = '\0'; - return stringLen; + return ss.size(); } - static void FormatArgumentAny(std::stringstream& ss, FormatToken token, const FormatArg_t& value) + static void FormatArgumentAny(FormatBuffer& ss, FormatToken token, const FormatArg_t& value) { if (std::holds_alternative(value)) { @@ -687,8 +684,7 @@ namespace OpenRCT2 } } - static void FormatStringAny( - std::stringstream& ss, const FmtString& fmt, const std::vector& args, size_t& argIndex) + static void FormatStringAny(FormatBuffer& ss, const FmtString& fmt, const std::vector& args, size_t& argIndex) { for (const auto& token : fmt) { @@ -735,7 +731,7 @@ namespace OpenRCT2 auto& ss = GetThreadFormatStream(); size_t argIndex = 0; FormatStringAny(ss, fmt, args, argIndex); - return ss.str(); + return ss.data(); } size_t FormatStringAny(char* buffer, size_t bufferLen, const FmtString& fmt, const std::vector& args) @@ -815,7 +811,7 @@ namespace OpenRCT2 return FormatStringAny(buffer, bufferLen, fmt, anyArgs); } - static void FormatMonthYear(std::stringstream& ss, int32_t month, int32_t year) + static void FormatMonthYear(FormatBuffer& ss, int32_t month, int32_t year) { thread_local std::vector tempArgs; tempArgs.clear(); diff --git a/src/openrct2/localisation/Formatting.h b/src/openrct2/localisation/Formatting.h index 9b039abd92..8c93e1cf6b 100644 --- a/src/openrct2/localisation/Formatting.h +++ b/src/openrct2/localisation/Formatting.h @@ -13,6 +13,7 @@ #include "FormatCodes.h" #include "Language.h" +#include #include #include #include @@ -23,6 +24,114 @@ namespace OpenRCT2 { + template class FormatBufferBase + { + T _storage[StackSize]; + T* _buffer; + size_t _size; + // NOTE: Capacity is on purpose uint32_t to have a fixed position for the flag on each architecture. + uint32_t _capacity; + + static constexpr uint32_t FlagLocalStorage = (1u << 31); + + public: + explicit FormatBufferBase() + : _storage{} + , _buffer(_storage) + , _size{} + , _capacity(FlagLocalStorage | static_cast(std::size(_storage))) + { + } + + ~FormatBufferBase() + { + if (_capacity & FlagLocalStorage) + return; + delete[] _buffer; + } + + size_t size() const + { + return _size; + } + + size_t capacity() const + { + return _capacity & ~FlagLocalStorage; + } + + void clear() + { + _size = 0; + _buffer[0] = T{}; + } + + const T* data() const + { + return _buffer; + } + + T* data() + { + return _buffer; + } + + auto& operator<<(const T* v) + { + if (!v) + return *this; + + append(v, LenFn(v)); + return *this; + } + + auto& operator<<(const T v) + { + append(&v, 1); + return *this; + } + + auto& operator<<(const std::basic_string_view v) + { + append(v.data(), v.size()); + return *this; + } + + private: + void append(const T* buf, size_t len) + { + ensure_capacity(len); + + std::copy(buf, buf + len, _buffer + _size); + + _size += len; + _buffer[_size] = T{}; + } + + void ensure_capacity(size_t additionalSize) + { + const size_t curSize = size(); + const size_t curCapacity = capacity(); + const bool isLocalStorage = _capacity & FlagLocalStorage; + + if (curSize + additionalSize < curCapacity) + return; + + const size_t newCapacity = (curCapacity + additionalSize + 1) << 1; + + T* newBuf = new T[newCapacity]; + std::copy(_buffer, _buffer + curSize, newBuf); + + if (!isLocalStorage) + delete[] _buffer; + + _capacity = static_cast(newCapacity); + _buffer = newBuf; + } + }; + + using FormatBuffer = FormatBufferBase; + using FormatArg_t = std::variant; class FmtString @@ -76,15 +185,15 @@ namespace OpenRCT2 std::string WithoutFormatTokens() const; }; - template void FormatArgument(std::stringstream& ss, FormatToken token, T arg); + template void FormatArgument(FormatBuffer& ss, FormatToken token, T arg); bool IsRealNameStringId(rct_string_id id); - void FormatRealName(std::stringstream& ss, rct_string_id id); + void FormatRealName(FormatBuffer& ss, rct_string_id id); FmtString GetFmtStringById(rct_string_id id); - std::stringstream& GetThreadFormatStream(); - size_t CopyStringStreamToBuffer(char* buffer, size_t bufferLen, std::stringstream& ss); + FormatBuffer& GetThreadFormatStream(); + size_t CopyStringStreamToBuffer(char* buffer, size_t bufferLen, FormatBuffer& ss); - inline void FormatString(std::stringstream& ss, std::stack& stack) + inline void FormatString(FormatBuffer& ss, std::stack& stack) { while (!stack.empty()) { @@ -103,7 +212,7 @@ namespace OpenRCT2 } template - static void FormatString(std::stringstream& ss, std::stack& stack, TArg0 arg0, TArgs&&... argN) + static void FormatString(FormatBuffer& ss, std::stack& stack, TArg0 arg0, TArgs&&... argN) { while (!stack.empty()) { @@ -144,7 +253,7 @@ namespace OpenRCT2 } } - template static void FormatString(std::stringstream& ss, const FmtString& fmt, TArgs&&... argN) + template static void FormatString(FormatBuffer& ss, const FmtString& fmt, TArgs&&... argN) { std::stack stack; stack.push(fmt.begin()); @@ -155,7 +264,7 @@ namespace OpenRCT2 { auto& ss = GetThreadFormatStream(); FormatString(ss, fmt, argN...); - return ss.str(); + return ss.data(); } template @@ -166,7 +275,7 @@ namespace OpenRCT2 return CopyStringStreamToBuffer(buffer, bufferLen, ss); } - template static void FormatStringId(std::stringstream& ss, rct_string_id id, TArgs&&... argN) + template static void FormatStringId(FormatBuffer& ss, rct_string_id id, TArgs&&... argN) { auto fmt = GetFmtStringById(id); FormatString(ss, fmt, argN...); diff --git a/test/tests/FormattingTests.cpp b/test/tests/FormattingTests.cpp index b34ec5e15b..373237df59 100644 --- a/test/tests/FormattingTests.cpp +++ b/test/tests/FormattingTests.cpp @@ -341,260 +341,260 @@ TEST_F(FormattingTests, using_legacy_buffer_args) TEST_F(FormattingTests, format_number_basic) { - std::stringstream ss; + FormatBuffer ss; // test basic integral conversion FormatArgument(ss, FormatToken::UInt16, 123); - ASSERT_STREQ("123", ss.str().c_str()); + ASSERT_STREQ("123", ss.data()); } TEST_F(FormattingTests, format_number_basic_int32) { - std::stringstream ss; + FormatBuffer ss; // test that case fallthrough works FormatArgument(ss, FormatToken::Int32, 123); - ASSERT_STREQ("123", ss.str().c_str()); + ASSERT_STREQ("123", ss.data()); } TEST_F(FormattingTests, format_number_negative) { - std::stringstream ss; + FormatBuffer ss; // test negative conversion FormatArgument(ss, FormatToken::Int32, -123); - ASSERT_STREQ("-123", ss.str().c_str()); + ASSERT_STREQ("-123", ss.data()); } TEST_F(FormattingTests, format_number_comma16_basic) { - std::stringstream ss; + FormatBuffer ss; // test separator formatter // test base case separator formatter FormatArgument(ss, FormatToken::Comma16, 123); - ASSERT_STREQ("123", ss.str().c_str()); + ASSERT_STREQ("123", ss.data()); } TEST_F(FormattingTests, format_number_comma16_negative) { - std::stringstream ss; + FormatBuffer ss; // test separator formatter // test base case separator formatter FormatArgument(ss, FormatToken::Comma16, -123); - ASSERT_STREQ("-123", ss.str().c_str()); + ASSERT_STREQ("-123", ss.data()); } TEST_F(FormattingTests, format_number_comma16_large) { - std::stringstream ss; + FormatBuffer ss; // test larger value for separator formatter FormatArgument(ss, FormatToken::Comma16, 123456789); - ASSERT_STREQ("123,456,789", ss.str().c_str()); + ASSERT_STREQ("123,456,789", ss.data()); } TEST_F(FormattingTests, format_number_comma16_large_negative) { - std::stringstream ss; + FormatBuffer ss; // test larger value for separator formatter with negative FormatArgument(ss, FormatToken::Comma16, -123456789); - ASSERT_STREQ("-123,456,789", ss.str().c_str()); + ASSERT_STREQ("-123,456,789", ss.data()); } TEST_F(FormattingTests, format_number_comma16_uneven) { - std::stringstream ss; + FormatBuffer ss; // test non-multiple of 3 FormatArgument(ss, FormatToken::Comma16, 12345678); - ASSERT_STREQ("12,345,678", ss.str().c_str()); + ASSERT_STREQ("12,345,678", ss.data()); } TEST_F(FormattingTests, format_number_comma16_uneven_negative) { - std::stringstream ss; + FormatBuffer ss; // test non-multiple of 3 with negative FormatArgument(ss, FormatToken::Comma16, -12345678); - ASSERT_STREQ("-12,345,678", ss.str().c_str()); + ASSERT_STREQ("-12,345,678", ss.data()); } TEST_F(FormattingTests, format_number_comma16_zero) { - std::stringstream ss; + FormatBuffer ss; // test zero FormatArgument(ss, FormatToken::Comma16, 0); - ASSERT_STREQ("0", ss.str().c_str()); + ASSERT_STREQ("0", ss.data()); } TEST_F(FormattingTests, format_number_comma1dp16_zero) { - std::stringstream ss; + FormatBuffer ss; // zero case FormatArgument(ss, FormatToken::Comma1dp16, 0); - ASSERT_STREQ("0.0", ss.str().c_str()); + ASSERT_STREQ("0.0", ss.data()); } TEST_F(FormattingTests, format_number_comma1dp16_leading_zero) { - std::stringstream ss; + FormatBuffer ss; // test leading zero FormatArgument(ss, FormatToken::Comma1dp16, 5); - ASSERT_STREQ("0.5", ss.str().c_str()); + ASSERT_STREQ("0.5", ss.data()); } TEST_F(FormattingTests, format_number_comma1dp16_leading_zero_negative) { - std::stringstream ss; + FormatBuffer ss; // test leading zero with negative value FormatArgument(ss, FormatToken::Comma1dp16, -5); - ASSERT_STREQ("-0.5", ss.str().c_str()); + ASSERT_STREQ("-0.5", ss.data()); } TEST_F(FormattingTests, format_number_comma1dp16_small_value) { - std::stringstream ss; + FormatBuffer ss; // test small value FormatArgument(ss, FormatToken::Comma1dp16, 75); - ASSERT_STREQ("7.5", ss.str().c_str()); + ASSERT_STREQ("7.5", ss.data()); } TEST_F(FormattingTests, format_number_comma1dp16_small_value_negative) { - std::stringstream ss; + FormatBuffer ss; // test small value with negative FormatArgument(ss, FormatToken::Comma1dp16, -75); - ASSERT_STREQ("-7.5", ss.str().c_str()); + ASSERT_STREQ("-7.5", ss.data()); } TEST_F(FormattingTests, format_number_comma1dp16_trailing_zeros) { - std::stringstream ss; + FormatBuffer ss; // test value with trailing zero, no commas FormatArgument(ss, FormatToken::Comma1dp16, 1000); - ASSERT_STREQ("100.0", ss.str().c_str()); + ASSERT_STREQ("100.0", ss.data()); } TEST_F(FormattingTests, format_number_comma1dp16_trailing_zeros_negative) { - std::stringstream ss; + FormatBuffer ss; // test value with trailing zero, no commas FormatArgument(ss, FormatToken::Comma1dp16, -1000); - ASSERT_STREQ("-100.0", ss.str().c_str()); + ASSERT_STREQ("-100.0", ss.data()); } TEST_F(FormattingTests, format_number_comma1dp16_large_trailing_zeros) { - std::stringstream ss; + FormatBuffer ss; // test value with commas and trailing zeros FormatArgument(ss, FormatToken::Comma1dp16, 10000000); - ASSERT_STREQ("1,000,000.0", ss.str().c_str()); + ASSERT_STREQ("1,000,000.0", ss.data()); } TEST_F(FormattingTests, format_number_comma1dp16_large_trailing_zeros_negative) { - std::stringstream ss; + FormatBuffer ss; // test value with commas and trailing zeros FormatArgument(ss, FormatToken::Comma1dp16, -10000000); - ASSERT_STREQ("-1,000,000.0", ss.str().c_str()); + ASSERT_STREQ("-1,000,000.0", ss.data()); } TEST_F(FormattingTests, format_number_comma1dp16_large_value) { - std::stringstream ss; + FormatBuffer ss; // test large value FormatArgument(ss, FormatToken::Comma1dp16, 123456789); - ASSERT_STREQ("12,345,678.9", ss.str().c_str()); + ASSERT_STREQ("12,345,678.9", ss.data()); } TEST_F(FormattingTests, format_number_comma1dp16_large_value_negative) { - std::stringstream ss; + FormatBuffer ss; // test large value FormatArgument(ss, FormatToken::Comma1dp16, -123456789); - ASSERT_STREQ("-12,345,678.9", ss.str().c_str()); + ASSERT_STREQ("-12,345,678.9", ss.data()); } TEST_F(FormattingTests, format_number_comma2dp32_zero) { - std::stringstream ss; + FormatBuffer ss; // zero case FormatArgument(ss, FormatToken::Comma2dp32, 0); - ASSERT_STREQ("0.00", ss.str().c_str()); + ASSERT_STREQ("0.00", ss.data()); } TEST_F(FormattingTests, format_number_comma2dp32_less_sig_figs) { - std::stringstream ss; + FormatBuffer ss; // test leading zero FormatArgument(ss, FormatToken::Comma2dp32, 5); - ASSERT_STREQ("0.05", ss.str().c_str()); + ASSERT_STREQ("0.05", ss.data()); } TEST_F(FormattingTests, format_number_comma2dp32_less_sig_figs_negative) { - std::stringstream ss; + FormatBuffer ss; // test leading zero FormatArgument(ss, FormatToken::Comma2dp32, -5); - ASSERT_STREQ("-0.05", ss.str().c_str()); + ASSERT_STREQ("-0.05", ss.data()); } TEST_F(FormattingTests, format_number_comma2dp32_leading_zero) { - std::stringstream ss; + FormatBuffer ss; // test small value FormatArgument(ss, FormatToken::Comma2dp32, 75); - ASSERT_STREQ("0.75", ss.str().c_str()); + ASSERT_STREQ("0.75", ss.data()); } TEST_F(FormattingTests, format_number_comma2dp32_leading_zero_negative) { - std::stringstream ss; + FormatBuffer ss; // test small value FormatArgument(ss, FormatToken::Comma2dp32, -75); - ASSERT_STREQ("-0.75", ss.str().c_str()); + ASSERT_STREQ("-0.75", ss.data()); } TEST_F(FormattingTests, format_number_comma2dp32_trailing_zeros) { - std::stringstream ss; + FormatBuffer ss; // test value with trailing zero, no commas FormatArgument(ss, FormatToken::Comma2dp32, 1000); - ASSERT_STREQ("10.00", ss.str().c_str()); + ASSERT_STREQ("10.00", ss.data()); } TEST_F(FormattingTests, format_number_comma2dp32_trailing_zeros_negative) { - std::stringstream ss; + FormatBuffer ss; // test value with trailing zero, no commas FormatArgument(ss, FormatToken::Comma2dp32, -1000); - ASSERT_STREQ("-10.00", ss.str().c_str()); + ASSERT_STREQ("-10.00", ss.data()); } TEST_F(FormattingTests, format_number_comma2dp32_large_trailing_zeros) { - std::stringstream ss; + FormatBuffer ss; // test value with commas and trailing zeros FormatArgument(ss, FormatToken::Comma2dp32, 10000000); - ASSERT_STREQ("100,000.00", ss.str().c_str()); + ASSERT_STREQ("100,000.00", ss.data()); } TEST_F(FormattingTests, format_number_comma2dp32_large_trailing_zeros_negative) { - std::stringstream ss; + FormatBuffer ss; // test value with commas and trailing zeros FormatArgument(ss, FormatToken::Comma2dp32, -10000000); - ASSERT_STREQ("-100,000.00", ss.str().c_str()); + ASSERT_STREQ("-100,000.00", ss.data()); } TEST_F(FormattingTests, format_number_comma2dp32_large_value) { - std::stringstream ss; + FormatBuffer ss; // test large value FormatArgument(ss, FormatToken::Comma2dp32, 123456789); - ASSERT_STREQ("1,234,567.89", ss.str().c_str()); + ASSERT_STREQ("1,234,567.89", ss.data()); } TEST_F(FormattingTests, format_number_comma2dp32_large_value_negative) { - std::stringstream ss; + FormatBuffer ss; // test large value FormatArgument(ss, FormatToken::Comma2dp32, -123456789); - ASSERT_STREQ("-1,234,567.89", ss.str().c_str()); + ASSERT_STREQ("-1,234,567.89", ss.data()); // extra note: // for some reason the FormatArgument function contains constexpr @@ -604,3 +604,14 @@ TEST_F(FormattingTests, format_number_comma2dp32_large_value_negative) // the necessary symbol to link with. // FormatArgument(ss, FormatToken::Comma1dp16, 12.372); } + +TEST_F(FormattingTests, buffer_storage_swap) +{ + FormatBufferBase ss; + ss << "Hello World"; + ASSERT_STREQ(ss.data(), "Hello World"); + ss << ", Exceeding local storage"; + ASSERT_STREQ(ss.data(), "Hello World, Exceeding local storage"); + ss << ", extended"; + ASSERT_STREQ(ss.data(), "Hello World, Exceeding local storage, extended"); +} From c2fd7708adef3f47eca770c7d05195bb31325212 Mon Sep 17 00:00:00 2001 From: Matt Date: Thu, 31 Dec 2020 15:57:36 +0200 Subject: [PATCH 2/3] Use char_traits for length --- src/openrct2/localisation/Formatting.h | 31 ++++++++++++++++++-------- test/tests/FormattingTests.cpp | 2 +- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/openrct2/localisation/Formatting.h b/src/openrct2/localisation/Formatting.h index 8c93e1cf6b..cf056c8160 100644 --- a/src/openrct2/localisation/Formatting.h +++ b/src/openrct2/localisation/Formatting.h @@ -24,13 +24,14 @@ namespace OpenRCT2 { - template class FormatBufferBase + template> class FormatBufferBase { T _storage[StackSize]; T* _buffer; size_t _size; // NOTE: Capacity is on purpose uint32_t to have a fixed position for the flag on each architecture. uint32_t _capacity; + TTraits _traits; static constexpr uint32_t FlagLocalStorage = (1u << 31); @@ -39,7 +40,7 @@ namespace OpenRCT2 : _storage{} , _buffer(_storage) , _size{} - , _capacity(FlagLocalStorage | static_cast(std::size(_storage))) + , _capacity(FlagLocalStorage | static_cast(StackSize)) { } @@ -76,12 +77,9 @@ namespace OpenRCT2 return _buffer; } - auto& operator<<(const T* v) + template auto& operator<<(T const (&v)[N]) { - if (!v) - return *this; - - append(v, LenFn(v)); + append(v, N); return *this; } @@ -91,13 +89,27 @@ namespace OpenRCT2 return *this; } + auto& operator<<(const T* v) + { + if (!v) + return *this; + + append(v, _traits.length(v)); + return *this; + } + auto& operator<<(const std::basic_string_view v) { append(v.data(), v.size()); return *this; } - private: + auto& operator<<(const std::basic_string& v) + { + append(v.data(), v.size()); + return *this; + } + void append(const T* buf, size_t len) { ensure_capacity(len); @@ -108,6 +120,7 @@ namespace OpenRCT2 _buffer[_size] = T{}; } + private: void ensure_capacity(size_t additionalSize) { const size_t curSize = size(); @@ -130,7 +143,7 @@ namespace OpenRCT2 } }; - using FormatBuffer = FormatBufferBase; + using FormatBuffer = FormatBufferBase; using FormatArg_t = std::variant; diff --git a/test/tests/FormattingTests.cpp b/test/tests/FormattingTests.cpp index 373237df59..7f7790b0b5 100644 --- a/test/tests/FormattingTests.cpp +++ b/test/tests/FormattingTests.cpp @@ -607,7 +607,7 @@ TEST_F(FormattingTests, format_number_comma2dp32_large_value_negative) TEST_F(FormattingTests, buffer_storage_swap) { - FormatBufferBase ss; + FormatBufferBase ss; ss << "Hello World"; ASSERT_STREQ(ss.data(), "Hello World"); ss << ", Exceeding local storage"; From 7d4637613a856b9be5eff28fbe5ff5530ccac22c Mon Sep 17 00:00:00 2001 From: Matt Date: Thu, 31 Dec 2020 15:59:15 +0200 Subject: [PATCH 3/3] Format the integers into temporary buffer to fix inline sprites --- src/openrct2/localisation/Formatting.cpp | 26 ++++++------------------ 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/src/openrct2/localisation/Formatting.cpp b/src/openrct2/localisation/Formatting.cpp index 30a2e5e355..57d2e0073d 100644 --- a/src/openrct2/localisation/Formatting.cpp +++ b/src/openrct2/localisation/Formatting.cpp @@ -593,31 +593,17 @@ namespace OpenRCT2 } break; case FormatToken::String: - if constexpr (std::is_same()) - { - if (arg != nullptr) - { - ss << arg; - } - } - else if constexpr (std::is_same()) - { - ss << arg.c_str(); - } - else if constexpr (std::is_same()) - { - ss << arg.c_str(); - } + ss << arg; break; case FormatToken::Sprite: if constexpr (std::is_integral()) { auto idx = static_cast(arg); - ss << "{INLINE_SPRITE}"; - ss << "{" << ((idx >> 0) & 0xFF) << "}"; - ss << "{" << ((idx >> 8) & 0xFF) << "}"; - ss << "{" << ((idx >> 16) & 0xFF) << "}"; - ss << "{" << ((idx >> 24) & 0xFF) << "}"; + char inlineBuf[64]; + size_t len = snprintf( + inlineBuf, sizeof(inlineBuf), "{INLINE_SPRITE}{%u}{%u}{%u}{%u}", ((idx >> 0) & 0xFF), + ((idx >> 8) & 0xFF), ((idx >> 16) & 0xFF), ((idx >> 24) & 0xFF)); + ss.append(inlineBuf, len); } break; default: