Replace std::stringstream with custom string buffer

This commit is contained in:
Matt 2020-12-30 15:14:27 +02:00
parent 5ae54eb9f9
commit 2ef4dd23aa
No known key found for this signature in database
GPG key ID: 6D4C24A61C93E208
3 changed files with 217 additions and 101 deletions

View file

@ -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<int32_t> 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<size_t TDecimalPlace, bool TDigitSep, typename T> void FormatNumber(std::stringstream& ss, T value)
template<size_t TDecimalPlace, bool TDigitSep, typename T> void FormatNumber(FormatBuffer& ss, T value)
{
char buffer[32];
size_t i = 0;
@ -379,7 +379,7 @@ namespace OpenRCT2
}
}
template<size_t TDecimalPlace, bool TDigitSep, typename T> void FormatCurrency(std::stringstream& ss, T rawValue)
template<size_t TDecimalPlace, bool TDigitSep, typename T> void FormatCurrency(FormatBuffer& ss, T rawValue)
{
auto currencyDesc = &CurrencyDescriptors[EnumValue(gConfigGeneral.currency_format)];
auto value = static_cast<int64_t>(rawValue) * currencyDesc->rate;
@ -434,7 +434,7 @@ namespace OpenRCT2
}
}
template<typename T> static void FormatMinutesSeconds(std::stringstream& ss, T value)
template<typename T> 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<typename T> static void FormatHoursMinutes(std::stringstream& ss, T value)
template<typename T> 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<typename T> void FormatArgument(std::stringstream& ss, FormatToken token, T arg)
template<typename T> 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<size_t>(bufferLen - 1, stringLen);
auto copyLen = std::min<size_t>(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<uint16_t>(value))
{
@ -687,8 +684,7 @@ namespace OpenRCT2
}
}
static void FormatStringAny(
std::stringstream& ss, const FmtString& fmt, const std::vector<FormatArg_t>& args, size_t& argIndex)
static void FormatStringAny(FormatBuffer& ss, const FmtString& fmt, const std::vector<FormatArg_t>& 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<FormatArg_t>& 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<FormatArg_t> tempArgs;
tempArgs.clear();

View file

@ -13,6 +13,7 @@
#include "FormatCodes.h"
#include "Language.h"
#include <cstring>
#include <sstream>
#include <stack>
#include <string>
@ -23,6 +24,114 @@
namespace OpenRCT2
{
template<typename T, size_t (*LenFn)(const T*), size_t StackSize = 256> 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<uint32_t>(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<T> 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<uint32_t>(newCapacity);
_buffer = newBuf;
}
};
using FormatBuffer = FormatBufferBase<char, strlen>;
using FormatArg_t = std::variant<uint16_t, int32_t, const char*, std::string>;
class FmtString
@ -76,15 +185,15 @@ namespace OpenRCT2
std::string WithoutFormatTokens() const;
};
template<typename T> void FormatArgument(std::stringstream& ss, FormatToken token, T arg);
template<typename T> 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<FmtString::iterator>& stack)
inline void FormatString(FormatBuffer& ss, std::stack<FmtString::iterator>& stack)
{
while (!stack.empty())
{
@ -103,7 +212,7 @@ namespace OpenRCT2
}
template<typename TArg0, typename... TArgs>
static void FormatString(std::stringstream& ss, std::stack<FmtString::iterator>& stack, TArg0 arg0, TArgs&&... argN)
static void FormatString(FormatBuffer& ss, std::stack<FmtString::iterator>& stack, TArg0 arg0, TArgs&&... argN)
{
while (!stack.empty())
{
@ -144,7 +253,7 @@ namespace OpenRCT2
}
}
template<typename... TArgs> static void FormatString(std::stringstream& ss, const FmtString& fmt, TArgs&&... argN)
template<typename... TArgs> static void FormatString(FormatBuffer& ss, const FmtString& fmt, TArgs&&... argN)
{
std::stack<FmtString::iterator> 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<typename... TArgs>
@ -166,7 +275,7 @@ namespace OpenRCT2
return CopyStringStreamToBuffer(buffer, bufferLen, ss);
}
template<typename... TArgs> static void FormatStringId(std::stringstream& ss, rct_string_id id, TArgs&&... argN)
template<typename... TArgs> static void FormatStringId(FormatBuffer& ss, rct_string_id id, TArgs&&... argN)
{
auto fmt = GetFmtStringById(id);
FormatString(ss, fmt, argN...);

View file

@ -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<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(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<double>(ss, FormatToken::Comma1dp16, 12.372);
}
TEST_F(FormattingTests, buffer_storage_swap)
{
FormatBufferBase<char, strlen, 16> 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");
}