1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2026-01-06 06:32:56 +01:00

Implement legacy format string

This commit is contained in:
Ted John
2020-10-12 20:25:22 +01:00
parent 6294188e93
commit 644f7f07b3
3 changed files with 139 additions and 7 deletions

View File

@@ -495,9 +495,33 @@ namespace OpenRCT2
return FmtString(std::move(fmtc));
}
std::stringstream& GetThreadFormatStream()
{
thread_local std::stringstream ss;
// Reset the buffer (reported as most efficient way)
std::stringstream().swap(ss);
return ss;
}
size_t CopyStringStreamToBuffer(char* buffer, size_t bufferLen, std::stringstream& ss)
{
auto stringLen = ss.tellp();
auto copyLen = std::min<size_t>(bufferLen - 1, stringLen);
ss.seekg(0, std::ios::beg);
ss.read(buffer, copyLen);
buffer[copyLen] = '\0';
return stringLen;
}
void FormatArgumentAny(std::stringstream& ss, FormatToken token, const std::any& value)
{
if (value.type() == typeid(int32_t))
if (value.type() == typeid(uint16_t))
{
FormatArgument(ss, token, std::any_cast<uint16_t>(value));
}
else if (value.type() == typeid(int32_t))
{
FormatArgument(ss, token, std::any_cast<int32_t>(value));
}
@@ -549,11 +573,81 @@ namespace OpenRCT2
std::string FormatStringAny(const FmtString& fmt, const std::vector<std::any>& args)
{
thread_local std::stringstream ss;
// Reset the buffer (reported as most efficient way)
std::stringstream().swap(ss);
auto& ss = GetThreadFormatStream();
size_t argIndex = 0;
FormatStringAny(ss, fmt, args, argIndex);
return ss.str();
}
size_t FormatStringAny(char* buffer, size_t bufferLen, const FmtString& fmt, const std::vector<std::any>& args)
{
auto& ss = GetThreadFormatStream();
size_t argIndex = 0;
FormatStringAny(ss, fmt, args, argIndex);
return CopyStringStreamToBuffer(buffer, bufferLen, ss);
}
template<typename T> static T ReadFromArgs(const void*& args)
{
T value;
std::memcpy(&value, args, sizeof(T));
args = reinterpret_cast<const char*>(reinterpret_cast<uintptr_t>(args) + sizeof(T));
return value;
}
static void BuildAnyArgListFromLegacyArgBuffer(const FmtString& fmt, std::vector<std::any>& anyArgs, const void* args)
{
for (const auto& t : fmt)
{
switch (t.kind)
{
case FORMAT_COMMA32:
case FORMAT_INT32:
case FORMAT_COMMA2DP32:
case FORMAT_CURRENCY2DP:
case FORMAT_CURRENCY:
anyArgs.push_back(ReadFromArgs<int32_t>(args));
break;
case FORMAT_COMMA16:
case FORMAT_UINT16:
case FORMAT_MONTHYEAR:
case FORMAT_MONTH:
case FORMAT_VELOCITY:
case FORMAT_DURATION:
case FORMAT_REALTIME:
case FORMAT_LENGTH:
anyArgs.push_back(ReadFromArgs<uint16_t>(args));
break;
case FORMAT_STRINGID:
case FORMAT_STRINGID2:
{
auto stringId = ReadFromArgs<rct_string_id>(args);
anyArgs.push_back(stringId);
BuildAnyArgListFromLegacyArgBuffer(GetFmtStringById(stringId), anyArgs, args);
break;
}
case FORMAT_STRING:
{
auto sz = ReadFromArgs<const char*>(args);
anyArgs.push_back(sz);
BuildAnyArgListFromLegacyArgBuffer(sz, anyArgs, args);
break;
}
case FORMAT_POP16:
args = reinterpret_cast<const char*>(reinterpret_cast<uintptr_t>(args) + 2);
break;
case FORMAT_PUSH16:
args = reinterpret_cast<const char*>(reinterpret_cast<uintptr_t>(args) - 2);
break;
}
}
}
size_t FormatStringLegacy(char* buffer, size_t bufferLen, rct_string_id id, const void* args)
{
std::vector<std::any> anyArgs;
auto fmt = GetFmtStringById(id);
BuildAnyArgListFromLegacyArgBuffer(fmt, anyArgs, args);
return FormatStringAny(buffer, bufferLen, fmt, anyArgs);
}
} // namespace OpenRCT2

View File

@@ -76,6 +76,8 @@ namespace OpenRCT2
bool CanFormatToken(FormatToken t);
FmtString GetFmtStringById(rct_string_id id);
std::stringstream& GetThreadFormatStream();
size_t CopyStringStreamToBuffer(char* buffer, size_t bufferLen, std::stringstream& ss);
inline void FormatString(std::stringstream& ss, std::stack<FmtString::iterator*> stack)
{
@@ -137,9 +139,7 @@ namespace OpenRCT2
template<typename... TArgs> std::string FormatString(const FmtString& fmt, TArgs&&... argN)
{
thread_local std::stringstream ss;
// Reset the buffer (reported as most efficient way)
std::stringstream().swap(ss);
auto& ss = GetThreadFormatStream();
FormatString(ss, fmt, argN...);
return ss.str();
}
@@ -156,5 +156,14 @@ namespace OpenRCT2
return FormatString(fmt, argN...);
}
template<typename... TArgs> size_t FormatStringId(char* buffer, size_t bufferLen, rct_string_id id, TArgs&&... argN)
{
auto& ss = GetThreadFormatStream();
FormatStringId(ss, id, argN...);
return CopyStringStreamToBuffer(buffer, bufferLen, ss);
}
std::string FormatStringAny(const FmtString& fmt, const std::vector<std::any>& args);
size_t FormatStringAny(char* buffer, size_t bufferLen, const FmtString& fmt, const std::vector<std::any>& args);
size_t FormatStringLegacy(char* buffer, size_t bufferLen, rct_string_id id, const void* args);
} // namespace OpenRCT2

View File

@@ -14,6 +14,7 @@
#include <openrct2/OpenRCT2.h>
#include <openrct2/config/Config.h>
#include <openrct2/core/String.hpp>
#include <openrct2/localisation/Localisation.h>
#include <openrct2/localisation/StringIds.h>
using namespace OpenRCT2;
@@ -280,3 +281,31 @@ TEST_F(FormattingTests, any_two_level_format)
auto actual = FormatStringAny("Queuing for {STRINGID}", { strDefault, strBoatHire, 2 });
ASSERT_EQ("Queuing for Boat Hire 2", actual);
}
TEST_F(FormattingTests, to_fixed_buffer)
{
char buffer[16];
std::memset(buffer, '\xFF', sizeof(buffer));
auto len = FormatStringId(buffer, 8, STR_GUEST_X, 123);
ASSERT_EQ(len, 9);
ASSERT_STREQ("Guest 1", buffer);
// Ensure rest of the buffer was not overwritten
for (size_t i = 8; i < sizeof(buffer); i++)
{
ASSERT_EQ('\xFF', buffer[i]);
}
}
TEST_F(FormattingTests, using_legacy_buffer_args)
{
auto ft = Formatter();
ft.Add<rct_string_id>(STR_RIDE_NAME_DEFAULT);
ft.Add<rct_string_id>(STR_RIDE_NAME_BOAT_HIRE);
ft.Add<uint16_t>(2);
char buffer[32]{};
auto len = FormatStringLegacy(buffer, sizeof(buffer), STR_QUEUING_FOR, ft.Data());
ASSERT_EQ(len, 23);
ASSERT_STREQ("Queuing for Boat Hire 2", buffer);
}