From 495298079bf32312a5acace2f277057ed643524a Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Wed, 6 Feb 2019 13:59:41 +0100 Subject: [PATCH] - rewrote the language table so that it doesn't have to reload everything on a language change. It now reads everything into a two-dimensional TMap and creates a list of mappings that apply to the current setting. The constant need for reloading was the main blocker in redesigning how Dehacked strings get inserted. Currently they override everything, but IWAD-based Dehacked text shouldn't block PWAD overrides from PWADs' LANGUAGE lumps and instead be treated as coming from an [en default] block. This also renames the main block from [enu default] to [en default], because it should be treated as the English default for all English locales and not just make it fall through to the base default as it did before. --- src/d_main.cpp | 2 +- src/doomstat.cpp | 4 +- src/gamedata/d_dehacked.cpp | 27 ++-- src/gamedata/stringtable.cpp | 293 +++++++---------------------------- src/gamedata/stringtable.h | 35 +++-- wadsrc/static/language.enu | 2 +- 6 files changed, 90 insertions(+), 273 deletions(-) diff --git a/src/d_main.cpp b/src/d_main.cpp index a2c758e0bc..b6c7237043 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -2427,7 +2427,7 @@ void D_DoomMain (void) } // [RH] Initialize localizable strings. - GStrings.LoadStrings (false); + GStrings.LoadStrings (); V_InitFontColors (); diff --git a/src/doomstat.cpp b/src/doomstat.cpp index b6f0e78710..c9ff769b54 100644 --- a/src/doomstat.cpp +++ b/src/doomstat.cpp @@ -56,10 +56,10 @@ CUSTOM_CVAR (Float, teamdamage, 0.f, CVAR_SERVERINFO) } } -CUSTOM_CVAR (String, language, "auto", CVAR_ARCHIVE) +CUSTOM_CVAR (String, language, "auto", CVAR_ARCHIVE|CVAR_NOINITCALL) { SetLanguageIDs (); - GStrings.LoadStrings (false); + GStrings.UpdateLanguage(); for (auto Level : AllLevels()) { // does this even make sense on secondary levels...? diff --git a/src/gamedata/d_dehacked.cpp b/src/gamedata/d_dehacked.cpp index dbf98a17f7..4643cd492c 100644 --- a/src/gamedata/d_dehacked.cpp +++ b/src/gamedata/d_dehacked.cpp @@ -285,7 +285,7 @@ static bool including, includenotext; static const char *unknown_str = "Unknown key %s encountered in %s %d.\n"; -static FStringTable *EnglishStrings; +static StringMap EnglishStrings, DehStrings; // This is an offset to be used for computing the text stuff. // Straight from the DeHackEd source which was @@ -2169,7 +2169,7 @@ static int PatchMusic (int dummy) keystring << "MUSIC_" << Line1; - GStrings.SetString (keystring, newname); + DehStrings.Insert(keystring, newname); DPrintf (DMSG_SPAMMY, "Music %s set to:\n%s\n", keystring.GetChars(), newname); } @@ -2280,11 +2280,11 @@ static int PatchText (int oldSize) const char *str; do { - str = EnglishStrings->MatchString(oldStr); + str = EnglishStrings.MatchString(oldStr); if (str != NULL) { - GStrings.SetString(str, newStr); - EnglishStrings->SetString(str, "~~"); // set to something invalid so that it won't get found again by the next iteration or by another replacement later + DehStrings.Insert(str, newStr); + EnglishStrings.Remove(str); // remove entry so that it won't get found again by the next iteration or by another replacement later good = true; } } @@ -2337,7 +2337,7 @@ static int PatchStrings (int dummy) // Account for a discrepancy between Boom's and ZDoom's name for the red skull key pickup message const char *ll = Line1; if (!stricmp(ll, "GOTREDSKULL")) ll = "GOTREDSKUL"; - GStrings.SetString (ll, holdstring); + DehStrings.Insert(ll, holdstring); DPrintf (DMSG_SPAMMY, "%s set to:\n%s\n", Line1, holdstring.GetChars()); } @@ -2670,11 +2670,6 @@ static void UnloadDehSupp () StyleNames.Reset(); AmmoNames.Reset(); UnchangedSpriteNames.Reset(); - if (EnglishStrings != NULL) - { - delete EnglishStrings; - EnglishStrings = NULL; - } } } @@ -2704,12 +2699,8 @@ static bool LoadDehSupp () return true; } - if (EnglishStrings == NULL) - { - EnglishStrings = new FStringTable; - EnglishStrings->LoadStrings (true); - } - + if (EnglishStrings.CountUsed() == 0) + EnglishStrings = GStrings.GetDefaultStrings(); UnchangedSpriteNames.Resize(sprites.Size()); for (unsigned i = 0; i < UnchangedSpriteNames.Size(); ++i) @@ -3079,6 +3070,8 @@ void FinishDehPatch () StateMap.ShrinkToFit(); TouchedActors.Clear(); TouchedActors.ShrinkToFit(); + EnglishStrings.Clear(); + GStrings.SetDehackedStrings(std::move(DehStrings)); // Now it gets nasty: We have to fiddle around with the weapons' ammo use info to make Doom's original // ammo consumption work as intended. diff --git a/src/gamedata/stringtable.cpp b/src/gamedata/stringtable.cpp index fc5e7ea8af..2fd41e94b8 100644 --- a/src/gamedata/stringtable.cpp +++ b/src/gamedata/stringtable.cpp @@ -43,119 +43,31 @@ #include "v_text.h" #include "gi.h" -// PassNum identifies which language pass this string is from. -// PassNum 0 is for DeHacked. -// PassNum 1 is for * strings. -// PassNum 2+ are for specific locales. -struct FStringTable::StringEntry -{ - StringEntry *Next; - char *Name; - uint8_t PassNum; - char String[]; -}; - -FStringTable::FStringTable () -{ - for (int i = 0; i < HASH_SIZE; ++i) - { - Buckets[i] = NULL; - } -} - -FStringTable::~FStringTable () -{ - FreeData (); -} - -void FStringTable::FreeData () -{ - for (int i = 0; i < HASH_SIZE; ++i) - { - StringEntry *entry = Buckets[i], *next; - Buckets[i] = NULL; - while (entry != NULL) - { - next = entry->Next; - M_Free (entry); - entry = next; - } - } -} - -void FStringTable::FreeNonDehackedStrings () -{ - for (int i = 0; i < HASH_SIZE; ++i) - { - StringEntry *entry, *next, **pentry; - - for (pentry = &Buckets[i], entry = *pentry; entry != NULL; ) - { - next = entry->Next; - if (entry->PassNum != 0) - { - *pentry = next; - M_Free (entry); - } - else - { - pentry = &entry->Next; - } - entry = next; - } - } -} - -void FStringTable::LoadStrings (bool enuOnly) +void FStringTable::LoadStrings () { int lastlump, lump; - int i, j; - - FreeNonDehackedStrings (); lastlump = 0; while ((lump = Wads.FindLump ("LANGUAGE", &lastlump)) != -1) { - j = 0; - if (!enuOnly) - { - LoadLanguage (lump, MAKE_ID('*',0,0,0), true, ++j); - for (i = 0; i < 4; ++i) - { - LoadLanguage (lump, LanguageIDs[i], true, ++j); - LoadLanguage (lump, LanguageIDs[i] & MAKE_ID(0xff,0xff,0,0), true, ++j); - LoadLanguage (lump, LanguageIDs[i], false, ++j); - } - } - - // Fill in any missing strings with the default language - LoadLanguage (lump, MAKE_ID('*','*',0,0), true, ++j); + LoadLanguage (lump); } + UpdateLanguage(); } -void FStringTable::LoadLanguage (int lumpnum, uint32_t code, bool exactMatch, int passnum) +void FStringTable::LoadLanguage (int lumpnum) { - static bool errordone = false; - const uint32_t orMask = exactMatch ? 0 : MAKE_ID(0,0,0xff,0); - uint32_t inCode = 0; - StringEntry *entry, **pentry; - uint32_t bucket; - int cmpval; - bool skip = true; - - code |= orMask; - + bool errordone = false; + TArray activeMaps; FScanner sc(lumpnum); sc.SetCMode (true); while (sc.GetString ()) { if (sc.Compare ("[")) { // Process language identifiers - bool donot = false; - bool forceskip = false; - skip = true; + activeMaps.Clear(); sc.MustGetString (); do { @@ -164,17 +76,18 @@ void FStringTable::LoadLanguage (int lumpnum, uint32_t code, bool exactMatch, in { if (len == 1 && sc.String[0] == '~') { - donot = true; + // deprecated and ignored + sc.ScriptMessage("Deprecated option '~' found in language list"); sc.MustGetString (); continue; } if (len == 1 && sc.String[0] == '*') { - inCode = MAKE_ID('*',0,0,0); + activeMaps.Push(MAKE_ID('*', 0, 0, 0)); } else if (len == 7 && stricmp (sc.String, "default") == 0) { - inCode = MAKE_ID('*','*',0,0); + activeMaps.Push(MAKE_ID('*', '*', 0, 0)); } else { @@ -184,31 +97,14 @@ void FStringTable::LoadLanguage (int lumpnum, uint32_t code, bool exactMatch, in } else { - inCode = MAKE_ID(tolower(sc.String[0]), tolower(sc.String[1]), tolower(sc.String[2]), 0); - } - if ((inCode | orMask) == code) - { - if (donot) - { - forceskip = true; - donot = false; - } - else - { - skip = false; - } + activeMaps.Push(MAKE_ID(tolower(sc.String[0]), tolower(sc.String[1]), tolower(sc.String[2]), 0)); } sc.MustGetString (); } while (!sc.Compare ("]")); - if (donot) - { - sc.ScriptError ("You must specify a language after ~"); - } - skip |= forceskip; } else { // Process string definitions. - if (inCode == 0) + if (activeMaps.Size() == 0) { // LANGUAGE lump is bad. We need to check if this is an old binary // lump and if so just skip it to allow old WADs to run which contain @@ -222,7 +118,7 @@ void FStringTable::LoadLanguage (int lumpnum, uint32_t code, bool exactMatch, in sc.ScriptError ("Found a string without a language specified."); } - bool savedskip = skip; + bool skip = false; if (sc.Compare("$")) { sc.MustGetStringName("ifgame"); @@ -234,20 +130,7 @@ void FStringTable::LoadLanguage (int lumpnum, uint32_t code, bool exactMatch, in } - if (skip) - { // We're not interested in this language, so skip the string. - sc.MustGetStringName ("="); - sc.MustGetString (); - do - { - sc.MustGetString (); - } - while (!sc.Compare (";")); - skip = savedskip; - continue; - } - - FString strName (sc.String); + FName strName (sc.String); sc.MustGetStringName ("="); sc.MustGetString (); FString strText (sc.String, ProcessEscapes (sc.String)); @@ -258,39 +141,36 @@ void FStringTable::LoadLanguage (int lumpnum, uint32_t code, bool exactMatch, in strText += sc.String; sc.MustGetString (); } - - // Does this string exist? If so, should we overwrite it? - bucket = MakeKey (strName.GetChars()) & (HASH_SIZE-1); - pentry = &Buckets[bucket]; - entry = *pentry; - cmpval = 1; - while (entry != NULL) + // Insert the string into all relevant tables. + for (auto map : activeMaps) { - cmpval = stricmp (entry->Name, strName.GetChars()); - if (cmpval >= 0) - break; - pentry = &entry->Next; - entry = *pentry; - } - if (cmpval == 0 && entry->PassNum >= passnum) - { - *pentry = entry->Next; - M_Free (entry); - entry = NULL; - } - if (entry == NULL || cmpval > 0) - { - entry = (StringEntry *)M_Malloc (sizeof(*entry) + strText.Len() + strName.Len() + 2); - entry->Next = *pentry; - *pentry = entry; - strcpy (entry->String, strText.GetChars()); - strcpy (entry->Name = entry->String + strText.Len() + 1, strName.GetChars()); - entry->PassNum = passnum; + allStrings[map].Insert(strName, strText); } } } } +void FStringTable::UpdateLanguage() +{ + currentLanguageSet.Clear(); + + auto checkone = [&](uint32_t lang_id) + { + auto list = allStrings.CheckKey(lang_id); + if (list && currentLanguageSet.Find(list) == currentLanguageSet.Size()) + currentLanguageSet.Push(list); + }; + + checkone(MAKE_ID('*', '*', '*', 0)); + checkone(MAKE_ID('*', 0, 0, 0)); + for (int i = 0; i < 4; ++i) + { + checkone(LanguageIDs[i]); + checkone(LanguageIDs[i] & MAKE_ID(0xff, 0xff, 0, 0)); + } + checkone(MAKE_ID('*', '*', 0, 0)); +} + // Replace \ escape sequences in a string with the escaped characters. size_t FStringTable::ProcessEscapes (char *iptr) { @@ -321,27 +201,20 @@ size_t FStringTable::ProcessEscapes (char *iptr) // Finds a string by name and returns its value const char *FStringTable::operator[] (const char *name) const { - if (name == NULL) + if (name == nullptr || *name == 0) { - return NULL; + return nullptr; } - uint32_t bucket = MakeKey (name) & (HASH_SIZE - 1); - StringEntry *entry = Buckets[bucket]; - - while (entry != NULL) + FName nm(name, true); + if (nm != NAME_None) { - int cmpval = stricmp (entry->Name, name); - if (cmpval == 0) + for (auto map : currentLanguageSet) { - return entry->String; + auto item = map->CheckKey(nm); + if (item) return item->GetChars(); } - if (cmpval == 1) - { - return NULL; - } - entry = entry->Next; } - return NULL; + return nullptr; } // Finds a string by name and returns its value. If the string does @@ -352,75 +225,19 @@ const char *FStringTable::operator() (const char *name) const return str ? str : name; } -// Find a string by name. pentry1 is a pointer to a pointer to it, and entry1 is a -// pointer to it. Return NULL for entry1 if it wasn't found. -void FStringTable::FindString (const char *name, StringEntry **&pentry1, StringEntry *&entry1) -{ - uint32_t bucket = MakeKey (name) & (HASH_SIZE - 1); - StringEntry **pentry = &Buckets[bucket], *entry = *pentry; - - while (entry != NULL) - { - int cmpval = stricmp (entry->Name, name); - if (cmpval == 0) - { - pentry1 = pentry; - entry1 = entry; - return; - } - if (cmpval == 1) - { - pentry1 = pentry; - entry1 = NULL; - return; - } - pentry = &entry->Next; - entry = *pentry; - } - pentry1 = pentry; - entry1 = entry; -} // Find a string with the same exact text. Returns its name. -const char *FStringTable::MatchString (const char *string) const +const char *StringMap::MatchString (const char *string) const { - for (int i = 0; i < HASH_SIZE; ++i) + StringMap::ConstIterator it(*this); + StringMap::ConstPair *pair; + + while (it.NextPair(pair)) { - for (StringEntry *entry = Buckets[i]; entry != NULL; entry = entry->Next) + if (pair->Value.Compare(string) == 0) { - if (strcmp (entry->String, string) == 0) - { - return entry->Name; - } + return pair->Key.GetChars(); } } - return NULL; -} - -void FStringTable::SetString (const char *name, const char *newString) -{ - StringEntry **pentry, *oentry; - FindString (name, pentry, oentry); - - size_t newlen = strlen (newString); - size_t namelen = strlen (name); - - // Create a new string entry - StringEntry *entry = (StringEntry *)M_Malloc (sizeof(*entry) + newlen + namelen + 2); - strcpy (entry->String, newString); - strcpy (entry->Name = entry->String + newlen + 1, name); - entry->PassNum = 0; - - // If this is a new string, insert it. Otherwise, replace the old one. - if (oentry == NULL) - { - entry->Next = *pentry; - *pentry = entry; - } - else - { - *pentry = entry; - entry->Next = oentry->Next; - M_Free (oentry); - } + return nullptr; } diff --git a/src/gamedata/stringtable.h b/src/gamedata/stringtable.h index 792ef06b3a..1c479ba5a5 100644 --- a/src/gamedata/stringtable.h +++ b/src/gamedata/stringtable.h @@ -44,34 +44,41 @@ #include +#include "doomdef.h" #include "doomtype.h" +// This public interface is for Dehacked +class StringMap : public TMap +{ +public: + const char *MatchString(const char *string) const; +}; + + class FStringTable { public: - struct StringEntry; + using LangMap = TMap; - FStringTable (); - ~FStringTable (); - - void LoadStrings (bool enuOnly); + void LoadStrings (); + void UpdateLanguage(); + StringMap GetDefaultStrings() { return allStrings[MAKE_ID('*', '*', 0, 0)]; } // Dehacked needs these for comparison + void SetDehackedStrings(StringMap && map) + { + allStrings.Insert(MAKE_ID('*', '*', '*', 0), map); + UpdateLanguage(); + } const char *operator() (const char *name) const; // Never returns NULL const char *operator[] (const char *name) const; // Can return NULL - const char *MatchString (const char *string) const; - void SetString (const char *name, const char *newString); - private: - enum { HASH_SIZE = 128 }; - StringEntry *Buckets[HASH_SIZE]; + LangMap allStrings; + TArray currentLanguageSet; - void FreeData (); - void FreeNonDehackedStrings (); - void LoadLanguage (int lumpnum, uint32_t code, bool exactMatch, int passnum); + void LoadLanguage (int lumpnum); static size_t ProcessEscapes (char *str); - void FindString (const char *stringName, StringEntry **&pentry, StringEntry *&entry); }; #endif //__STRINGTABLE_H__ diff --git a/wadsrc/static/language.enu b/wadsrc/static/language.enu index 54cb51ef14..7ad4b6ca7c 100644 --- a/wadsrc/static/language.enu +++ b/wadsrc/static/language.enu @@ -1,6 +1,6 @@ /* U.S. English. (Sorry, it's not English English.) */ -[enu default] +[en default] SECRETMESSAGE = "A secret is revealed!";