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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user