From 1f21e9dc7411917e7fd7b7aaa764e75bb09d5aea Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Sat, 7 Dec 2024 09:28:56 +0000 Subject: [PATCH] Codechange: String parameter encoding for regular strings. This allows a string and its parameters to be encoded and stored as just one string, instead of juggling with capturing and restoring string parameters. The advantage of EncodedStrings over raw strings is they use current language and parameter values at the point of decoding. --- src/string.cpp | 1 + src/strings.cpp | 79 ++++++++++++++++++++++++++++++++++++--- src/strings_func.h | 16 ++++++++ src/strings_type.h | 23 ++++++++++++ src/table/control_codes.h | 2 +- 5 files changed, 114 insertions(+), 7 deletions(-) diff --git a/src/string.cpp b/src/string.cpp index 9803cc9b5a..8266b8527e 100644 --- a/src/string.cpp +++ b/src/string.cpp @@ -100,6 +100,7 @@ static bool IsSccEncodedCode(char32_t c) switch (c) { case SCC_RECORD_SEPARATOR: case SCC_ENCODED: + case SCC_ENCODED_INTERNAL: case SCC_ENCODED_NUMERIC: case SCC_ENCODED_STRING: return true; diff --git a/src/strings.cpp b/src/strings.cpp index 9255b9d618..7979ab984d 100644 --- a/src/strings.cpp +++ b/src/strings.cpp @@ -95,6 +95,65 @@ const StringParameter &StringParameters::GetNextParameterReference() return param; } +/** + * Encode a string with no parameters into an encoded string. + * @param str The StringID to format. + * @returns The encoded string. + */ +EncodedString GetEncodedString(StringID str) +{ + return GetEncodedStringWithArgs(str, {}); +} + +/** + * Encode a string with its parameters into an encoded string. + * The encoded string can be stored and decoded later without requiring parameters to be stored separately. + * @param str The StringID to format. + * @param params The parameters of the string. + * @returns The encoded string. + */ +EncodedString GetEncodedStringWithArgs(StringID str, std::span params) +{ + std::string result; + auto output = std::back_inserter(result); + Utf8Encode(output, SCC_ENCODED_INTERNAL); + fmt::format_to(output, "{:X}", str); + + struct visitor { + std::back_insert_iterator &output; + + void operator()(const std::monostate &) {} + + void operator()(const uint64_t &arg) + { + Utf8Encode(output, SCC_ENCODED_NUMERIC); + fmt::format_to(this->output, "{:X}", arg); + } + + void operator()(const std::string &value) + { + Utf8Encode(output, SCC_ENCODED_STRING); + fmt::format_to(this->output, "{}", value); + } + }; + + visitor v{output}; + for (const auto ¶m : params) { + *output = SCC_RECORD_SEPARATOR; + std::visit(v, param.data); + } + + return EncodedString{std::move(result)}; +} + +/** + * Decode the encoded string. + * @returns Decoded raw string. + */ +std::string EncodedString::GetDecodedString() const +{ + return GetString(STR_JUST_RAW_STRING, this->string); +} /** * Set a string parameter \a v at index \a n in the global string parameter array. @@ -956,10 +1015,11 @@ uint ConvertDisplaySpeedToKmhishSpeed(uint speed, VehicleType type) /** * Decodes an encoded string during FormatString. * @param str The buffer of the encoded string. + * @param game_script Set if decoding a GameScript-encoded string. This affects how string IDs are handled. * @param builder The string builder to write the string to. * @returns Updated position position in input buffer. */ -static const char *DecodeEncodedString(const char *str, StringBuilder &builder) +static const char *DecodeEncodedString(const char *str, bool game_script, StringBuilder &builder) { ArrayStringParameters<20> sub_args; @@ -970,7 +1030,7 @@ static const char *DecodeEncodedString(const char *str, StringBuilder &builder) builder += "(invalid SCC_ENCODED)"; return p; } - if (id >= TAB_SIZE_GAMESCRIPT) { + if (game_script && id >= TAB_SIZE_GAMESCRIPT) { while (*p != '\0') p++; builder += "(invalid StringID)"; return p; @@ -984,6 +1044,12 @@ static const char *DecodeEncodedString(const char *str, StringBuilder &builder) /* Find end of the parameter. */ for (; *p != '\0' && *p != SCC_RECORD_SEPARATOR; ++p) {} + if (s == p) { + /* This is an empty parameter. */ + sub_args.SetParam(i++, std::monostate{}); + continue; + } + /* Get the parameter type. */ char32_t parameter_type; size_t len = Utf8Decode(¶meter_type, s); @@ -1014,13 +1080,13 @@ static const char *DecodeEncodedString(const char *str, StringBuilder &builder) } default: - /* Skip unknown parameter. */ - i++; + /* Unknown parameter, make it blank. */ + sub_args.SetParam(i++, std::monostate{}); break; } } - StringID stringid = MakeStringID(TEXT_TAB_GAMESCRIPT_START, id); + StringID stringid = game_script ? MakeStringID(TEXT_TAB_GAMESCRIPT_START, id) : StringID{id.base()}; GetStringWithArgs(builder, stringid, sub_args, true); return p; @@ -1092,7 +1158,8 @@ static void FormatString(StringBuilder &builder, const char *str_arg, StringPara args.SetTypeOfNextParameter(b); switch (b) { case SCC_ENCODED: - str = DecodeEncodedString(str, builder); + case SCC_ENCODED_INTERNAL: + str = DecodeEncodedString(str, b == SCC_ENCODED, builder); break; case SCC_NEWGRF_STRINL: { diff --git a/src/strings_func.h b/src/strings_func.h index 2748ff6758..307d57e7bc 100644 --- a/src/strings_func.h +++ b/src/strings_func.h @@ -135,6 +135,22 @@ std::string GetString(StringID string, Args &&... args) return GetStringWithArgs(string, params); } +EncodedString GetEncodedString(StringID str); +EncodedString GetEncodedStringWithArgs(StringID str, std::span params); + +/** + * Get an encoded string with parameters. + * @param string String ID to encode. + * @param args The parameters to set. + * @return The encoded string. + */ +template +EncodedString GetEncodedString(StringID string, const Args&... args) +{ + auto params = MakeParameters(std::forward(args)...); + return GetEncodedStringWithArgs(string, params); +} + /** * A searcher for missing glyphs. */ diff --git a/src/strings_type.h b/src/strings_type.h index d749ac7960..567fd04e7a 100644 --- a/src/strings_type.h +++ b/src/strings_type.h @@ -92,4 +92,27 @@ struct StringParameter { inline StringParameter(const ConvertibleThroughBase auto &data) : data(static_cast(data.base())), type(0) {} }; +/** + * Container for an encoded string, created by GetEncodedString. + */ +class EncodedString { +public: + EncodedString() = default; + + auto operator<=>(const EncodedString &) const = default; + + std::string GetDecodedString() const; + + inline void clear() { this->string.clear(); } + inline bool empty() const { return this->string.empty(); } + +private: + std::string string; ///< The encoded string. + + /* An EncodedString can only be created by GetEncodedStringWithArgs(). */ + explicit EncodedString(std::string &&string) : string(std::move(string)) {} + + friend EncodedString GetEncodedStringWithArgs(StringID str, std::span params); +}; + #endif /* STRINGS_TYPE_H */ diff --git a/src/table/control_codes.h b/src/table/control_codes.h index 5e2e4ea9c6..d6db08ad31 100644 --- a/src/table/control_codes.h +++ b/src/table/control_codes.h @@ -25,7 +25,7 @@ enum StringControlCode : uint16_t { /* All SCC_ENCODED* control codes must have stable ids are they are stored in strings that are saved in savegames. */ SCC_ENCODED = SCC_CONTROL_START, ///< Encoded string marker and sub-string parameter. - SCC_ENCODED_RESERVED, ///< Reserved for future non-GS encoded strings. + SCC_ENCODED_INTERNAL, ///< Encoded text from OpenTTD. SCC_ENCODED_NUMERIC, ///< Encoded numeric parameter. SCC_ENCODED_STRING, ///< Encoded string parameter.