- 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.
This commit is contained in:
Christoph Oelckers 2019-02-06 13:59:41 +01:00
parent 7fa3081581
commit 495298079b
6 changed files with 90 additions and 273 deletions

View file

@ -2427,7 +2427,7 @@ void D_DoomMain (void)
}
// [RH] Initialize localizable strings.
GStrings.LoadStrings (false);
GStrings.LoadStrings ();
V_InitFontColors ();

View file

@ -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...?

View file

@ -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.

View file

@ -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<uint32_t> 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,37 +141,34 @@ void FStringTable::LoadLanguage (int lumpnum, uint32_t code, bool exactMatch, in
strText += sc.String;
sc.MustGetString ();
}
// Insert the string into all relevant tables.
for (auto map : activeMaps)
{
allStrings[map].Insert(strName, strText);
}
}
}
}
// 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)
void FStringTable::UpdateLanguage()
{
currentLanguageSet.Clear();
auto checkone = [&](uint32_t lang_id)
{
cmpval = stricmp (entry->Name, strName.GetChars());
if (cmpval >= 0)
break;
pentry = &entry->Next;
entry = *pentry;
}
if (cmpval == 0 && entry->PassNum >= passnum)
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)
{
*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;
}
}
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.
@ -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;
}

View file

@ -44,34 +44,41 @@
#include <stdlib.h>
#include "doomdef.h"
#include "doomtype.h"
// This public interface is for Dehacked
class StringMap : public TMap<FName, FString>
{
public:
const char *MatchString(const char *string) const;
};
class FStringTable
{
public:
struct StringEntry;
using LangMap = TMap<uint32_t, StringMap>;
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<StringMap*> 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__

View file

@ -1,6 +1,6 @@
/* U.S. English. (Sorry, it's not English English.) */
[enu default]
[en default]
SECRETMESSAGE = "A secret is revealed!";