I hope I don't regret doing this so close before I want to do a release, since it's a pretty

major change to ACS's workings. However, I had an epiphany yesterday and just had to do this, since it seems like too big a deal to hold off until a later release:
- Dynamically generated strings returned via strparam and get(user)cvar now last as long as they
  need to. They do not disappear at the end of each tic. You can now safely store them in
  variables and hold on to them indefinitely. In addition, strings from libraries no longer
  require you to load the exact same libraries in the exact same order. You can even store a
  library's string in a world variable and retrieve it on another map that doesn't load the
  library at all, and it will still be the correct string.
- ACS library IDs now only get 12 bits instead of 16 so that each string table can hold up
  to about a million strings instead of just 65536. This shouldn't be a problem, although it
  means that save games that had strings with the larger IDs stored in variables are no
  longer compatible. Since many saves don't involve libraries at all, and even many that do 
  are not actually affected, I'm not bumping the min save version. The worst that can happen
  is that you get no text at all where some was expected.

SVN r4295 (trunk)
This commit is contained in:
Randy Heit 2013-05-30 02:22:47 +00:00
parent 493edd2df0
commit 0eb72156ed
4 changed files with 767 additions and 63 deletions

View file

@ -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();
}
}
//==========================================================================

View file

@ -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();
}
//==========================================================================

View file

@ -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 *> 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<FString>
{
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<FString>::Push(str) | ACSSTRING_OR_ONTHEFLY;
}
}
ACS_StringsOnTheFly;
TArray<FString> 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;

View file

@ -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<PoolEntry> 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<ProfileCollector> &profiles);
friend void ArrangeFunctionProfiles(TArray<ProfileCollector> &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