diff --git a/src/g_level.cpp b/src/g_level.cpp index 526ec2a22..755a4fba6 100644 --- a/src/g_level.cpp +++ b/src/g_level.cpp @@ -737,6 +737,9 @@ void G_DoCompleted (void) if (!(level.flags2 & LEVEL2_FORGETSTATE)) { G_SnapshotLevel (); + // Do not free any global strings this level might reference + // while it's not loaded. + FBehavior::StaticLockLevelVarStrings(); } else { // Make sure we don't have a snapshot lying around from before. @@ -1575,6 +1578,11 @@ void G_UnSnapshotLevel (bool hubLoad) } // No reason to keep the snapshot around once the level's been entered. level.info->ClearSnapshot(); + if (hubLoad) + { + // Unlock ACS global strings that were locked when the snapshot was made. + FBehavior::StaticUnlockLevelVarStrings(); + } } //========================================================================== diff --git a/src/g_mapinfo.cpp b/src/g_mapinfo.cpp index cb7b03932..7a8075174 100644 --- a/src/g_mapinfo.cpp +++ b/src/g_mapinfo.cpp @@ -198,6 +198,9 @@ void G_ClearSnapshots (void) { wadlevelinfos[i].ClearSnapshot(); } + // Since strings are only locked when snapshotting a level, unlock them + // all now, since we got rid of all the snapshots that cared about them. + GlobalACSStrings.UnlockAll(); } //========================================================================== diff --git a/src/p_acs.cpp b/src/p_acs.cpp index 903ea9cf9..71c0f53f3 100644 --- a/src/p_acs.cpp +++ b/src/p_acs.cpp @@ -80,7 +80,6 @@ extern FILE *Logfile; FRandom pr_acs ("ACS"); - // I imagine this much stack space is probably overkill, but it could // potentially get used with recursive functions. #define STACK_SIZE 4096 @@ -142,47 +141,6 @@ struct FBehavior::ArrayInfo }; TArray FBehavior::StaticModules; - - -//============================================================================ -// -// Global and world variables -// -//============================================================================ - -// ACS variables with world scope -SDWORD ACS_WorldVars[NUM_WORLDVARS]; -FWorldGlobalArray ACS_WorldArrays[NUM_WORLDVARS]; - -// ACS variables with global scope -SDWORD ACS_GlobalVars[NUM_GLOBALVARS]; -FWorldGlobalArray ACS_GlobalArrays[NUM_GLOBALVARS]; - - -//============================================================================ -// -// On the fly strings -// -//============================================================================ - -#define LIB_ACSSTRINGS_ONTHEFLY 0x7fff -#define ACSSTRING_OR_ONTHEFLY (LIB_ACSSTRINGS_ONTHEFLY<<16) - -class OnTheFlyArray : public TArray -{ -public: - // Returns a valid string identifier for this tick, or - // -1 if we ran out of room. - int Push(FString &str) - { - if (Size() >= 0x10000) - { - return -1; - } - return (int)TArray::Push(str) | ACSSTRING_OR_ONTHEFLY; - } -} -ACS_StringsOnTheFly; TArray ACS_StringBuilderStack; #define STRINGBUILDER_START(Builder) if (Builder.IsNotEmpty() || ACS_StringBuilderStack.Size()) { ACS_StringBuilderStack.Push(Builder); Builder = ""; } @@ -209,6 +167,571 @@ inline int uallong(const int &foo) } #endif +//============================================================================ +// +// Global and world variables +// +//============================================================================ + +// ACS variables with world scope +SDWORD ACS_WorldVars[NUM_WORLDVARS]; +FWorldGlobalArray ACS_WorldArrays[NUM_WORLDVARS]; + +// ACS variables with global scope +SDWORD ACS_GlobalVars[NUM_GLOBALVARS]; +FWorldGlobalArray ACS_GlobalArrays[NUM_GLOBALVARS]; + + +//---------------------------------------------------------------------------- +// +// Global ACS strings (Formerly known as On the fly strings) +// +// This special string table is part of the global state. Programmatically +// generated strings (e.g. those returned by strparam) are stored here. +// PCD_TAGSTRING also now stores strings in this table instead of simply +// tagging strings with their library ID. +// +// Identical strings map to identical string identifiers. +// +// When the string table needs to grow to hold more strings, a garbage +// collection is first attempted to see if more room can be made to store +// strings without growing. A string is concidered in use if any value +// in any of these variable blocks contains a valid ID in the global string +// table: +// * All running scripts' local variables +// * All map variables +// * All world variables +// * All global variables +// It's not important whether or not they are really used as strings, only +// that they might be. A string is also concidered in use if its lock count +// is non-zero, even if none of the above variable blocks referenced it. +// +// To keep track of local and map variables for nonresident maps in a hub, +// when a map's state is archived, all strings found in its local and map +// variables are locked. When a map is revisited in a hub, all strings found +// in its local and map variables are unlocked. Locking and unlocking are +// cumulative operations. +// +// What this all means is that: +// * Strings returned by strparam last indefinitely. No longer do they +// disappear at the end of the tic they were generated. +// * You can pass library strings around freely without having to worry +// about always having the same libraries loaded in the same order on +// every map that needs to use those strings. +// +//---------------------------------------------------------------------------- + +ACSStringPool GlobalACSStrings; + +ACSStringPool::ACSStringPool() +{ + memset(PoolBuckets, 0xFF, sizeof(PoolBuckets)); + FirstFreeEntry = 0; +} + +//============================================================================ +// +// ACSStringPool :: Clear +// +// Remove all strings from the pool. +// +//============================================================================ + +void ACSStringPool::Clear() +{ + Pool.Clear(); + memset(PoolBuckets, 0xFF, sizeof(PoolBuckets)); + FirstFreeEntry = 0; +} + +//============================================================================ +// +// ACSStringPool :: AddString +// +// Returns a valid string identifier (including library ID) or -1 if we ran +// out of room. Identical strings will return identical values. +// +//============================================================================ + +int ACSStringPool::AddString(const char *str) +{ + size_t len = strlen(str); + unsigned int h = SuperFastHash(str, len); + unsigned int bucketnum = h % NUM_BUCKETS; + int i = FindString(str, len, h, bucketnum); + if (i >= 0) + { + return i | STRPOOL_LIBRARYID_OR; + } + FString fstr(str); + return InsertString(fstr, h, bucketnum); +} + +int ACSStringPool::AddString(FString &str) +{ + unsigned int h = SuperFastHash(str.GetChars(), str.Len()); + unsigned int bucketnum = h % NUM_BUCKETS; + int i = FindString(str, str.Len(), h, bucketnum); + if (i >= 0) + { + return i | STRPOOL_LIBRARYID_OR; + } + return InsertString(str, h, bucketnum); +} + +//============================================================================ +// +// ACSStringPool :: GetString +// +//============================================================================ + +const char *ACSStringPool::GetString(int strnum) +{ + assert((strnum & LIBRARYID_MASK) == STRPOOL_LIBRARYID_OR); + strnum &= ~LIBRARYID_MASK; + if ((unsigned)strnum < Pool.Size() && Pool[strnum].Next != FREE_ENTRY) + { + return Pool[strnum].Str; + } + return NULL; +} + +//============================================================================ +// +// ACSStringPool :: LockString +// +// Prevents this string from being purged. +// +//============================================================================ + +void ACSStringPool::LockString(int strnum) +{ + assert((strnum & LIBRARYID_MASK) == STRPOOL_LIBRARYID_OR); + strnum &= ~LIBRARYID_MASK; + assert((unsigned)strnum < Pool.Size()); + Pool[strnum].LockCount++; +} + +//============================================================================ +// +// ACSStringPool :: UnlockString +// +// When equally mated with LockString, allows this string to be purged. +// +//============================================================================ + +void ACSStringPool::UnlockString(int strnum) +{ + assert((strnum & LIBRARYID_MASK) == STRPOOL_LIBRARYID_OR); + strnum &= ~LIBRARYID_MASK; + assert((unsigned)strnum < Pool.Size()); + assert(Pool[strnum].LockCount > 0); + Pool[strnum].LockCount--; +} + +//============================================================================ +// +// ACSStringPool :: MarkString +// +// Prevent this string from being purged during the next call to PurgeStrings. +// This does not carry over to subsequent calls of PurgeStrings. +// +//============================================================================ + +void ACSStringPool::MarkString(int strnum) +{ + assert((strnum & LIBRARYID_MASK) == STRPOOL_LIBRARYID_OR); + strnum &= ~LIBRARYID_MASK; + assert((unsigned)strnum < Pool.Size()); + Pool[strnum].LockCount |= 0x80000000; +} + +//============================================================================ +// +// ACSStringPool :: LockStringArray +// +// Prevents several strings from being purged. Entries not in this pool will +// be silently ignored. The idea here is to pass this function a block of +// ACS variables. Everything that looks like it might be a string in the pool +// is locked, even if it's not actually used as such. It's better to keep +// more strings than we need than to throw away ones we do need. +// +//============================================================================ + +void ACSStringPool::LockStringArray(const int *strnum, unsigned int count) +{ + for (unsigned int i = 0; i < count; ++i) + { + int num = strnum[i]; + if ((num & LIBRARYID_MASK) == STRPOOL_LIBRARYID_OR) + { + num &= ~LIBRARYID_MASK; + if ((unsigned)num < Pool.Size()) + { + Pool[num].LockCount++; + } + } + } +} + +//============================================================================ +// +// ACSStringPool :: UnlockStringArray +// +// Reverse of LockStringArray. +// +//============================================================================ + +void ACSStringPool::UnlockStringArray(const int *strnum, unsigned int count) +{ + for (unsigned int i = 0; i < count; ++i) + { + int num = strnum[i]; + if ((num & LIBRARYID_MASK) == STRPOOL_LIBRARYID_OR) + { + num &= ~LIBRARYID_MASK; + if ((unsigned)num < Pool.Size()) + { + assert(Pool[num].LockCount > 0); + Pool[num].LockCount--; + } + } + } +} + +//============================================================================ +// +// ACSStringPool :: MarkStringArray +// +// Array version of MarkString. +// +//============================================================================ + +void ACSStringPool::MarkStringArray(const int *strnum, unsigned int count) +{ + for (unsigned int i = 0; i < count; ++i) + { + int num = strnum[i]; + if ((num & LIBRARYID_MASK) == STRPOOL_LIBRARYID_OR) + { + num &= ~LIBRARYID_MASK; + if ((unsigned)num < Pool.Size()) + { + Pool[num].LockCount |= 0x80000000; + } + } + } +} + +//============================================================================ +// +// ACSStringPool :: MarkStringMap +// +// World/global variables version of MarkString. +// +//============================================================================ + +void ACSStringPool::MarkStringMap(const FWorldGlobalArray &aray) +{ + FWorldGlobalArray::ConstIterator it(aray); + FWorldGlobalArray::ConstPair *pair; + + while (it.NextPair(pair)) + { + int num = pair->Value; + if ((num & LIBRARYID_MASK) == STRPOOL_LIBRARYID_OR) + { + num &= ~LIBRARYID_MASK; + if ((unsigned)num < Pool.Size()) + { + Pool[num].LockCount |= 0x80000000; + } + } + } +} + +//============================================================================ +// +// ACSStringPool :: UnlockAll +// +// Resets every entry's lock count to 0. Used when doing a partial reset of +// ACS state such as travelling to a new hub. +// +//============================================================================ + +void ACSStringPool::UnlockAll() +{ + for (unsigned int i = 0; i < Pool.Size(); ++i) + { + Pool[i].LockCount = 0; + } +} + +//============================================================================ +// +// ACSStringPool :: PurgeStrings +// +// Remove all unlocked strings from the pool. +// +//============================================================================ + +void ACSStringPool::PurgeStrings() +{ + // Clear the hash buckets. We'll rebuild them as we decide what strings + // to keep and which to toss. + memset(PoolBuckets, 0xFF, sizeof(PoolBuckets)); + for (unsigned int i = 0; i < Pool.Size(); ++i) + { + PoolEntry *entry = &Pool[i]; + if (entry->Next != FREE_ENTRY) + { + if (entry->LockCount == 0) + { // Mark this entry as free. + entry->Next = FREE_ENTRY; + if (i < FirstFreeEntry) + { + FirstFreeEntry = i; + } + } + else + { + // Rehash this entry. + unsigned int h = entry->Hash % NUM_BUCKETS; + entry->Next = PoolBuckets[h]; + PoolBuckets[h] = i; + // Remove MarkString's mark. + entry->LockCount &= 0x7FFFFFFF; + } + } + } +} + +//============================================================================ +// +// ACSStringPool :: FindString +// +// Finds a string in the pool. Does not include the library ID in the returned +// value. Returns -1 if the string does not exist in the pool. +// +//============================================================================ + +int ACSStringPool::FindString(const char *str, size_t len, unsigned int h, unsigned int bucketnum) +{ + unsigned int i = PoolBuckets[bucketnum]; + while (i != NO_ENTRY) + { + PoolEntry *entry = &Pool[i]; + assert(entry->Next != FREE_ENTRY); + if (entry->Hash == h && entry->Str.Len() == len && + memcmp(entry->Str.GetChars(), str, len) == 0) + { + return i; + } + i = entry->Next; + } + return -1; +} + +//============================================================================ +// +// ACSStringPool :: InsertString +// +// Inserts a new string into the pool. +// +//============================================================================ + +int ACSStringPool::InsertString(FString &str, unsigned int h, unsigned int bucketnum) +{ + if (Pool.Size() >= STRPOOL_LIBRARYID) + { + return -1; + } + unsigned int index = FirstFreeEntry; + if (index >= MIN_GC_SIZE && index == Pool.Max()) + { // We will need to grow the array. Try a garbage collection first. + P_CollectACSGlobalStrings(); + } + if (index == Pool.Size()) + { // There were no free entries; make a new one. + Pool.Reserve(1); + FirstFreeEntry++; + } + else + { // Scan for the next free entry + unsigned int i; + for (i = FirstFreeEntry + 1; i < Pool.Size() && Pool[i].Next != FREE_ENTRY; ++i) + { + } + FirstFreeEntry = i; + } + PoolEntry *entry = &Pool[index]; + entry->Str = str; + entry->Hash = h; + entry->Next = PoolBuckets[bucketnum]; + entry->LockCount = 0; + PoolBuckets[bucketnum] = index; + return index | STRPOOL_LIBRARYID_OR; +} + +//============================================================================ +// +// ACSStringPool :: ReadStrings +// +// Reads strings from a PNG chunk. +// +//============================================================================ + +void ACSStringPool::ReadStrings(PNGHandle *png, DWORD id) +{ + Clear(); + + size_t len = M_FindPNGChunk(png, id); + if (len != 0) + { + FPNGChunkArchive arc(png->File->GetFile(), id, len); + int32 i, j, poolsize; + unsigned int h, bucketnum; + char *str = NULL; + + arc << poolsize; + + Pool.Resize(poolsize); + i = 0; + j = arc.ReadCount(); + while (j >= 0) + { + // Mark skipped entries as free + for (; i < j; ++i) + { + Pool[i].Next = FREE_ENTRY; + Pool[i].LockCount = 0; + } + arc << str; + h = SuperFastHash(str, strlen(str)); + bucketnum = h % NUM_BUCKETS; + Pool[i].Str = str; + Pool[i].Hash = h; + Pool[i].LockCount = arc.ReadCount(); + Pool[i].Next = PoolBuckets[bucketnum]; + PoolBuckets[bucketnum] = i; + i++; + j = arc.ReadCount(); + } + if (str != NULL) + { + delete[] str; + } + } +} + +//============================================================================ +// +// ACSStringPool :: WriteStrings +// +// Writes strings to a PNG chunk. +// +//============================================================================ + +void ACSStringPool::WriteStrings(FILE *file, DWORD id) const +{ + int32 i, poolsize = (int32)Pool.Size(); + + if (poolsize == 0) + { // No need to write if we don't have anything. + return; + } + FPNGChunkArchive arc(file, id); + + arc << poolsize; + for (i = 0; i < poolsize; ++i) + { + PoolEntry *entry = &Pool[i]; + if (entry->Next != FREE_ENTRY) + { + arc.WriteCount(i); + arc.WriteString(entry->Str); + arc.WriteCount(entry->LockCount); + } + } + arc.WriteCount(-1); +} + +//============================================================================ +// +// ACSStringPool :: Dump +// +// Lists all strings in the pool. +// +//============================================================================ + +void ACSStringPool::Dump() const +{ + for (unsigned int i = 0; i < Pool.Size(); ++i) + { + if (Pool[i].Next != FREE_ENTRY) + { + Printf("%4u. (%2d) \"%s\"\n", i, Pool[i].LockCount, Pool[i].Str.GetChars()); + } + } +} + +//============================================================================ +// +// P_MarkWorldVarStrings +// +//============================================================================ + +void P_MarkWorldVarStrings() +{ + GlobalACSStrings.MarkStringArray(ACS_WorldVars, countof(ACS_WorldVars)); + for (size_t i = 0; i < countof(ACS_WorldArrays); ++i) + { + GlobalACSStrings.MarkStringMap(ACS_WorldArrays[i]); + } +} + +//============================================================================ +// +// P_MarkGlobalVarStrings +// +//============================================================================ + +void P_MarkGlobalVarStrings() +{ + GlobalACSStrings.MarkStringArray(ACS_GlobalVars, countof(ACS_GlobalVars)); + for (size_t i = 0; i < countof(ACS_GlobalArrays); ++i) + { + GlobalACSStrings.MarkStringMap(ACS_GlobalArrays[i]); + } +} + +//============================================================================ +// +// P_CollectACSGlobalStrings +// +// Garbage collect ACS global strings. +// +//============================================================================ + +void P_CollectACSGlobalStrings() +{ + FBehavior::StaticMarkLevelVarStrings(); + P_MarkWorldVarStrings(); + P_MarkGlobalVarStrings(); + GlobalACSStrings.PurgeStrings(); +} + +#ifdef _DEBUG +CCMD(acsgc) +{ + P_CollectACSGlobalStrings(); +} +CCMD(globstr) +{ + GlobalACSStrings.Dump(); +} +#endif + //============================================================================ // // ScriptPresentation @@ -236,7 +759,7 @@ static FString ScriptPresentation(int script) //============================================================================ // -// +// P_ClearACSVars // //============================================================================ @@ -256,12 +779,22 @@ void P_ClearACSVars(bool alsoglobal) { ACS_GlobalArrays[i].Clear (); } + // Since we cleared all ACS variables, we know nothing refers to them + // anymore. + GlobalACSStrings.Clear(); + } + else + { + // Purge any strings that aren't referenced by global variables, since + // they're the only possible references left. + P_MarkGlobalVarStrings(); + GlobalACSStrings.PurgeStrings(); } } //============================================================================ // -// +// WriteVars // //============================================================================ @@ -419,6 +952,7 @@ void P_ReadACSVars(PNGHandle *png) ReadVars (png, ACS_GlobalVars, NUM_GLOBALVARS, MAKE_ID('g','v','A','r')); ReadArrayVars (png, ACS_WorldArrays, NUM_WORLDVARS, MAKE_ID('w','a','R','r')); ReadArrayVars (png, ACS_GlobalArrays, NUM_GLOBALVARS, MAKE_ID('g','a','R','r')); + GlobalACSStrings.ReadStrings(png, MAKE_ID('a','s','T','r')); } //============================================================================ @@ -433,6 +967,7 @@ void P_WriteACSVars(FILE *stdfile) WriteVars (stdfile, ACS_GlobalVars, NUM_GLOBALVARS, MAKE_ID('g','v','A','r')); WriteArrayVars (stdfile, ACS_WorldArrays, NUM_WORLDVARS, MAKE_ID('w','a','R','r')); WriteArrayVars (stdfile, ACS_GlobalArrays, NUM_GLOBALVARS, MAKE_ID('g','a','R','r')); + GlobalACSStrings.WriteStrings(stdfile, MAKE_ID('a','s','T','r')); } //---- Inventory functions --------------------------------------// @@ -904,6 +1439,84 @@ FBehavior *FBehavior::StaticGetModule (int lib) return StaticModules[lib]; } +void FBehavior::StaticMarkLevelVarStrings() +{ + // Mark map variables. + for (DWORD modnum = 0; modnum < StaticModules.Size(); ++modnum) + { + StaticModules[modnum]->MarkMapVarStrings(); + } + // Mark running scripts' local variables. + if (DACSThinker::ActiveThinker != NULL) + { + for (DLevelScript *script = DACSThinker::ActiveThinker->Scripts; script != NULL; script = script->GetNext()) + { + script->MarkLocalVarStrings(); + } + } +} + +void FBehavior::StaticLockLevelVarStrings() +{ + // Lock map variables. + for (DWORD modnum = 0; modnum < StaticModules.Size(); ++modnum) + { + StaticModules[modnum]->LockMapVarStrings(); + } + // Lock running scripts' local variables. + if (DACSThinker::ActiveThinker != NULL) + { + for (DLevelScript *script = DACSThinker::ActiveThinker->Scripts; script != NULL; script = script->GetNext()) + { + script->LockLocalVarStrings(); + } + } +} + +void FBehavior::StaticUnlockLevelVarStrings() +{ + // Unlock map variables. + for (DWORD modnum = 0; modnum < StaticModules.Size(); ++modnum) + { + StaticModules[modnum]->UnlockMapVarStrings(); + } + // Unlock running scripts' local variables. + if (DACSThinker::ActiveThinker != NULL) + { + for (DLevelScript *script = DACSThinker::ActiveThinker->Scripts; script != NULL; script = script->GetNext()) + { + script->UnlockLocalVarStrings(); + } + } +} + +void FBehavior::MarkMapVarStrings() const +{ + GlobalACSStrings.MarkStringArray(MapVarStore, NUM_MAPVARS); + for (int i = 0; i < NumArrays; ++i) + { + GlobalACSStrings.MarkStringArray(ArrayStore[i].Elements, ArrayStore[i].ArraySize); + } +} + +void FBehavior::LockMapVarStrings() const +{ + GlobalACSStrings.LockStringArray(MapVarStore, NUM_MAPVARS); + for (int i = 0; i < NumArrays; ++i) + { + GlobalACSStrings.LockStringArray(ArrayStore[i].Elements, ArrayStore[i].ArraySize); + } +} + +void FBehavior::UnlockMapVarStrings() const +{ + GlobalACSStrings.UnlockStringArray(MapVarStore, NUM_MAPVARS); + for (int i = 0; i < NumArrays; ++i) + { + GlobalACSStrings.UnlockStringArray(ArrayStore[i].Elements, ArrayStore[i].ArraySize); + } +} + void FBehavior::StaticSerializeModuleStates (FArchive &arc) { DWORD modnum; @@ -1055,7 +1668,7 @@ FBehavior::FBehavior (int lumpnum, FileReader * fr, int len) // 1. If not, corrupt modules cause memory leaks // 2. Corrupt modules won't be reported when a level is being loaded if this function quits before // adding it to the list. - LibraryID = StaticModules.Push (this) << 16; + LibraryID = StaticModules.Push (this) << LIBRARYID_SHIFT; if (fr == NULL) len = Wads.LumpLength (lumpnum); @@ -1167,7 +1780,7 @@ FBehavior::FBehavior (int lumpnum, FileReader * fr, int len) { MapVars[i] = &MapVarStore[i]; } - //LibraryID = StaticModules.Push (this) << 16; + //LibraryID = StaticModules.Push (this) << LIBRARYID_SHIFT; } else { @@ -1267,7 +1880,8 @@ FBehavior::FBehavior (int lumpnum, FileReader * fr, int len) { for (DWORD i = 0; i < chunk[1]/4; ++i) { - MapVarStore[chunk[i+2]] |= LibraryID; +// MapVarStore[chunk[i+2]] |= LibraryID; + MapVarStore[chunk[i+2]] = GlobalACSStrings.AddString(LookupString(MapVarStore[chunk[i+2]])); } } @@ -1282,7 +1896,8 @@ FBehavior::FBehavior (int lumpnum, FileReader * fr, int len) SDWORD *elems = ArrayStore[arraynum].Elements; for (int j = ArrayStore[arraynum].ArraySize; j > 0; --j, ++elems) { - *elems |= LibraryID; +// *elems |= LibraryID; + *elems = GlobalACSStrings.AddString(LookupString(*elems)); } } } @@ -1306,8 +1921,14 @@ FBehavior::FBehavior (int lumpnum, FileReader * fr, int len) { // For ATAG, a value of 0 = Integer, 1 = String, 2 = FunctionPtr // Our implementation uses the same tags for both String and FunctionPtr - if(*chunkData) + if (*chunkData == 2) + { *elems |= LibraryID; + } + else if (*chunkData == 1) + { + *elems = GlobalACSStrings.AddString(LookupString(*elems)); + } } i += 4+ArrayStore[arraynum].ArraySize; } @@ -1942,15 +2563,12 @@ BYTE *FBehavior::NextChunk (BYTE *chunk) const const char *FBehavior::StaticLookupString (DWORD index) { - DWORD lib = index >> 16; - - switch (lib) - { - case LIB_ACSSTRINGS_ONTHEFLY: - index &= 0xffff; - return (ACS_StringsOnTheFly.Size() > index) ? ACS_StringsOnTheFly[index].GetChars() : NULL; - } + DWORD lib = index >> LIBRARYID_SHIFT; + if (lib == STRPOOL_LIBRARYID) + { + return GlobalACSStrings.GetString(index); + } if (lib >= (DWORD)StaticModules.Size()) { return NULL; @@ -2167,7 +2785,7 @@ void DACSThinker::Tick () script = next; } - ACS_StringsOnTheFly.Clear(); +// GlobalACSStrings.Clear(); if (ACS_StringBuilderStack.Size()) { @@ -2233,7 +2851,7 @@ void DLevelScript::Serialize (FArchive &arc) if (arc.IsStoring ()) { - WORD lib = activeBehavior->GetLibraryID() >> 16; + WORD lib = activeBehavior->GetLibraryID() >> LIBRARYID_SHIFT; arc << lib; i = activeBehavior->PC2Ofs (pc); arc << i; @@ -3639,7 +4257,7 @@ static int DoGetCVar(FBaseCVar *cvar, bool is_string) if (is_string) { val = cvar->GetGenericRep(CVAR_String); - return ACS_StringsOnTheFly.Push(FString(val.String)); + return GlobalACSStrings.AddString(val.String); } else if (cvar->GetRealType() == CVAR_Float) { @@ -4385,7 +5003,8 @@ int DLevelScript::RunScript () break; case PCD_TAGSTRING: - Stack[sp-1] |= activeBehavior->GetLibraryID(); + //Stack[sp-1] |= activeBehavior->GetLibraryID(); + Stack[sp-1] = GlobalACSStrings.AddString(activeBehavior->LookupString(Stack[sp-1])); break; case PCD_PUSHNUMBER: @@ -7485,7 +8104,7 @@ scriptwait: case PCD_SAVESTRING: // Saves the string { - PushToStack(ACS_StringsOnTheFly.Push(work)); + PushToStack(GlobalACSStrings.AddString(work)); STRINGBUILDER_FINISH(work); } break; diff --git a/src/p_acs.h b/src/p_acs.h index e4eadf3b0..c9c999556 100644 --- a/src/p_acs.h +++ b/src/p_acs.h @@ -69,6 +69,56 @@ extern FWorldGlobalArray ACS_WorldArrays[NUM_WORLDVARS]; extern SDWORD ACS_GlobalVars[NUM_GLOBALVARS]; extern FWorldGlobalArray ACS_GlobalArrays[NUM_GLOBALVARS]; +#define LIBRARYID_MASK 0xFFF00000 +#define LIBRARYID_SHIFT 20 + +// Global ACS string table +#define STRPOOL_LIBRARYID (INT_MAX >> LIBRARYID_SHIFT) +#define STRPOOL_LIBRARYID_OR (STRPOOL_LIBRARYID << LIBRARYID_SHIFT) + +class ACSStringPool +{ +public: + ACSStringPool(); + int AddString(const char *str); + int AddString(FString &str); + const char *GetString(int strnum); + void LockString(int strnum); + void UnlockString(int strnum); + void UnlockAll(); + void MarkString(int strnum); + void LockStringArray(const int *strnum, unsigned int count); + void UnlockStringArray(const int *strnum, unsigned int count); + void MarkStringArray(const int *strnum, unsigned int count); + void MarkStringMap(const FWorldGlobalArray &array); + void PurgeStrings(); + void Clear(); + void Dump() const; + void ReadStrings(PNGHandle *png, DWORD id); + void WriteStrings(FILE *file, DWORD id) const; + +private: + int FindString(const char *str, size_t len, unsigned int h, unsigned int bucketnum); + int InsertString(FString &str, unsigned int h, unsigned int bucketnum); + + enum { NUM_BUCKETS = 251 }; + enum { FREE_ENTRY = 0xFFFFFFFE }; // Stored in PoolEntry's Next field + enum { NO_ENTRY = 0xFFFFFFFF }; + enum { MIN_GC_SIZE = 100 }; // Don't auto-collect until there are this many strings + struct PoolEntry + { + FString Str; + unsigned int Hash; + unsigned int Next; + unsigned int LockCount; + }; + TArray Pool; + unsigned int PoolBuckets[NUM_BUCKETS]; + unsigned int FirstFreeEntry; +}; +extern ACSStringPool GlobalACSStrings; + +void P_CollectACSGlobalStrings(); void P_ReadACSVars(PNGHandle *); void P_WriteACSVars(FILE*); void P_ClearACSVars(bool); @@ -204,6 +254,8 @@ public: const char *GetModuleName() const { return ModuleName; } ACSProfileInfo *GetFunctionProfileData(int index) { return index >= 0 && index < NumFunctions ? &FunctionProfileData[index] : NULL; } ACSProfileInfo *GetFunctionProfileData(ScriptFunction *func) { return GetFunctionProfileData((int)(func - (ScriptFunction *)Functions)); } + const char *LookupString (DWORD index) const; + SDWORD *MapVars[NUM_MAPVARS]; static FBehavior *StaticLoadModule (int lumpnum, FileReader * fr=NULL, int len=0); @@ -212,6 +264,9 @@ public: static bool StaticCheckAllGood (); static FBehavior *StaticGetModule (int lib); static void StaticSerializeModuleStates (FArchive &arc); + static void StaticMarkLevelVarStrings(); + static void StaticLockLevelVarStrings(); + static void StaticUnlockLevelVarStrings(); static const ScriptPtr *StaticFindScript (int script, FBehavior *&module); static const char *StaticLookupString (DWORD index); @@ -251,11 +306,14 @@ private: void UnencryptStrings (); void UnescapeStringTable(BYTE *chunkstart, BYTE *datastart, bool haspadding); int FindStringInChunk (DWORD *chunk, const char *varname) const; - const char *LookupString (DWORD index) const; void SerializeVars (FArchive &arc); void SerializeVarSet (FArchive &arc, SDWORD *vars, int max); + void MarkMapVarStrings() const; + void LockMapVarStrings() const; + void UnlockMapVarStrings() const; + friend void ArrangeScriptProfiles(TArray &profiles); friend void ArrangeFunctionProfiles(TArray &profiles); }; @@ -727,6 +785,21 @@ public: inline void SetState (EScriptState newstate) { state = newstate; } inline EScriptState GetState () { return state; } + DLevelScript *GetNext() const { return next; } + + void MarkLocalVarStrings() const + { + GlobalACSStrings.MarkStringArray(localvars, numlocalvars); + } + void LockLocalVarStrings() const + { + GlobalACSStrings.LockStringArray(localvars, numlocalvars); + } + void UnlockLocalVarStrings() const + { + GlobalACSStrings.UnlockStringArray(localvars, numlocalvars); + } + protected: DLevelScript *next, *prev; int script; @@ -804,6 +877,7 @@ private: DLevelScript *Scripts; // List of all running scripts friend class DLevelScript; + friend class FBehavior; }; // The structure used to control scripts between maps