/* ** p_acs.h ** ACS script stuff ** **--------------------------------------------------------------------------- ** Copyright 1998-2012 Randy Heit ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** 3. The name of the author may not be used to endorse or promote products ** derived from this software without specific prior written permission. ** ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. **--------------------------------------------------------------------------- ** */ #ifndef __P_ACS_H__ #define __P_ACS_H__ #include "doomtype.h" #include "dthinker.h" #define LOCAL_SIZE 20 #define NUM_MAPVARS 128 class FFont; class FileReader; struct line_t; class FSerializer; enum { NUM_WORLDVARS = 256, NUM_GLOBALVARS = 64 }; struct InitIntToZero { void Init(int &v) { v = 0; } }; typedef TMap, InitIntToZero> FWorldGlobalArray; // Type of elements count is unsigned int instead of size_t to match ACSStringPool interface template struct BoundsCheckingArray { T &operator[](const unsigned int index) { if (index >= N) { I_Error("Out of bounds memory access in ACS VM"); } return buffer[index]; } T *Pointer() { return buffer; } unsigned int Size() const { return N; } void Fill(const T &value) { std::fill(std::begin(buffer), std::end(buffer), value); } private: T buffer[N]; }; // ACS variables with global scope extern BoundsCheckingArray ACS_GlobalVars; extern BoundsCheckingArray ACS_GlobalArrays; #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 levelnum, int strnum); void UnlockAll(); void MarkString(int strnum); void LockStringArray(int levelnum, 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 UnlockForLevel(int level) ; void ReadStrings(FSerializer &file, const char *key); void WriteStrings(FSerializer &file, const char *key) 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); void FindFirstFreeEntry(unsigned int base); 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 = FREE_ENTRY; bool Mark; TArray Locks; void Lock(int levelnum); void Unlock(int levelnum); }; TArray Pool; unsigned int PoolBuckets[NUM_BUCKETS]; unsigned int FirstFreeEntry; }; extern ACSStringPool GlobalACSStrings; void P_CollectACSGlobalStrings(); void P_ReadACSVars(FSerializer &); void P_WriteACSVars(FSerializer &); void P_ClearACSVars(bool); struct ACSProfileInfo { unsigned long long TotalInstr; unsigned int NumRuns; unsigned int MinInstrPerRun; unsigned int MaxInstrPerRun; ACSProfileInfo(); void AddRun(unsigned int num_instr); void Reset(); }; struct ProfileCollector { ACSProfileInfo *ProfileData; class FBehavior *Module; int Index; }; class ACSLocalVariables { public: ACSLocalVariables(TArray &variables) : memory(&variables[0]) , count(variables.Size()) { } void Reset(int32_t *const memory, const size_t count) { // TODO: pointer sanity check? // TODO: constraints on count? this->memory = memory; this->count = count; } int32_t& operator[](const size_t index) { if (index >= count) { I_Error("Out of bounds access to local variables in ACS VM"); } return memory[index]; } const int32_t *GetPointer() const { return memory; } private: int32_t *memory; size_t count; }; struct ACSLocalArrayInfo { unsigned int Size; int Offset; }; struct ACSLocalArrays { unsigned int Count; ACSLocalArrayInfo *Info; ACSLocalArrays() { Count = 0; Info = NULL; } ~ACSLocalArrays() { if (Info != NULL) { delete[] Info; Info = NULL; } } // Bounds-checking Set and Get for local arrays void Set(ACSLocalVariables &locals, int arraynum, int arrayentry, int value) { if ((unsigned int)arraynum < Count && (unsigned int)arrayentry < Info[arraynum].Size) { locals[Info[arraynum].Offset + arrayentry] = value; } } int Get(ACSLocalVariables &locals, int arraynum, int arrayentry) { if ((unsigned int)arraynum < Count && (unsigned int)arrayentry < Info[arraynum].Size) { return locals[Info[arraynum].Offset + arrayentry]; } return 0; } }; // The in-memory version struct ScriptPtr { int Number; uint32_t Address; uint8_t Type; uint8_t ArgCount; uint16_t VarCount; uint16_t Flags; ACSLocalArrays LocalArrays; ACSProfileInfo ProfileData; }; // The present ZDoom version struct ScriptPtr3 { int16_t Number; uint8_t Type; uint8_t ArgCount; uint32_t Address; }; // The intermediate ZDoom version struct ScriptPtr1 { int16_t Number; uint16_t Type; uint32_t Address; uint32_t ArgCount; }; // The old Hexen version struct ScriptPtr2 { uint32_t Number; // Type is Number / 1000 uint32_t Address; uint32_t ArgCount; }; struct ScriptFlagsPtr { uint16_t Number; uint16_t Flags; }; struct ScriptFunctionInFile { uint8_t ArgCount; uint8_t LocalCount; uint8_t HasReturnValue; uint8_t ImportNum; uint32_t Address; }; struct ScriptFunction { uint8_t ArgCount; uint8_t HasReturnValue; uint8_t ImportNum; int LocalCount; uint32_t Address; ACSLocalArrays LocalArrays; }; // Script types enum { SCRIPT_Closed = 0, SCRIPT_Open = 1, SCRIPT_Respawn = 2, SCRIPT_Death = 3, SCRIPT_Enter = 4, SCRIPT_Pickup = 5, SCRIPT_BlueReturn = 6, SCRIPT_RedReturn = 7, SCRIPT_WhiteReturn = 8, SCRIPT_Lightning = 12, SCRIPT_Unloading = 13, SCRIPT_Disconnect = 14, SCRIPT_Return = 15, SCRIPT_Event = 16, // [BB] SCRIPT_Kill = 17, // [JM] SCRIPT_Reopen = 18, // [Nash] }; // Script flags enum { SCRIPTF_Net = 0x0001 // Safe to "puke" in multiplayer }; enum ACSFormat { ACS_Old, ACS_Enhanced, ACS_LittleEnhanced, ACS_Unknown }; class FBehavior { public: FBehavior (); ~FBehavior (); bool Init(FLevelLocals *l, int lumpnum, FileReader * fr = NULL, int len = 0); bool IsGood (); uint8_t *FindChunk (uint32_t id) const; uint8_t *NextChunk (uint8_t *chunk) const; const ScriptPtr *FindScript (int number) const; void StartTypedScripts (uint16_t type, AActor *activator, bool always, int arg1, bool runNow); uint32_t PC2Ofs (int *pc) const { return (uint32_t)((uint8_t *)pc - Data); } int *Ofs2PC (uint32_t ofs) const { return (int *)(Data + ofs); } int *Jump2PC (uint32_t jumpPoint) const { return Ofs2PC(JumpPoints[jumpPoint]); } ACSFormat GetFormat() const { return Format; } ScriptFunction *GetFunction (int funcnum, FBehavior *&module) const; int GetArrayVal (int arraynum, int index) const; void SetArrayVal (int arraynum, int index, int value); inline bool CopyStringToArray(int arraynum, int index, int maxLength, const char * string); int FindFunctionName (const char *funcname) const; int FindMapVarName (const char *varname) const; int FindMapArray (const char *arrayname) const; int GetLibraryID () const { return LibraryID; } int *GetScriptAddress (const ScriptPtr *ptr) const { return (int *)(ptr->Address + Data); } int GetScriptIndex (const ScriptPtr *ptr) const { ptrdiff_t index = ptr - Scripts; return index >= NumScripts ? -1 : (int)index; } ScriptPtr *GetScriptPtr(int index) const { return index >= 0 && index < NumScripts ? &Scripts[index] : NULL; } int GetLumpNum() const { return LumpNum; } int GetDataSize() const { return DataSize; } 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 (uint32_t index) const; BoundsCheckingArray MapVars; private: struct ArrayInfo; ACSFormat Format; FLevelLocals *Level; int LumpNum; uint8_t *Data; int DataSize; uint8_t *Chunks; ScriptPtr *Scripts; int NumScripts; ScriptFunction *Functions; ACSProfileInfo *FunctionProfileData; int NumFunctions; ArrayInfo *ArrayStore; int NumArrays; ArrayInfo **Arrays; int NumTotalArrays; uint32_t StringTable; int32_t MapVarStore[NUM_MAPVARS]; TArray Imports; uint32_t LibraryID; char ModuleName[9]; TArray JumpPoints; void LoadScriptsDirectory (); static int SortScripts (const void *a, const void *b); void UnencryptStrings (); void UnescapeStringTable(uint8_t *chunkstart, uint8_t *datastart, bool haspadding); int FindStringInChunk (uint32_t *chunk, const char *varname) const; void SerializeVars (FSerializer &arc); void SerializeVarSet (FSerializer &arc, int32_t *vars, int max); void MarkMapVarStrings() const; void LockMapVarStrings(int levelnum) const; friend struct FBehaviorContainer; }; struct FBehaviorContainer { FLevelLocals *Level; TArray StaticModules; FBehaviorContainer(FLevelLocals *l) : Level(l) {} FBehavior *LoadModule(int lumpnum, FileReader *fr = nullptr, int len = 0); void LoadDefaultModules(); void UnloadModules(); bool CheckAllGood(); FBehavior *GetModule(int lib); void SerializeModuleStates(FSerializer &arc); void MarkLevelVarStrings(); void LockLevelVarStrings(int levelnum); void UnlockLevelVarStrings(int levelnum); const ScriptPtr *FindScript(int script, FBehavior *&module); const char *LookupString(uint32_t index); void StartTypedScripts(uint16_t type, AActor *activator, bool always, int arg1 = 0, bool runNow = false); void StopMyScripts(AActor *actor); void ArrangeScriptProfiles(TArray &profiles); void ArrangeFunctionProfiles(TArray &profiles); }; class DLevelScript; class DACSThinker : public DThinker { DECLARE_CLASS(DACSThinker, DThinker) HAS_OBJECT_POINTERS public: static const int DEFAULT_STAT = STAT_SCRIPTS; void Construct() {} ~DACSThinker(); void Serialize(FSerializer &arc); void Tick(); typedef TMap ScriptMap; ScriptMap RunningScripts; // Array of all synchronous scripts void DumpScriptStatus(); void StopScriptsFor(AActor *actor); private: DLevelScript *LastScript = nullptr; DLevelScript *Scripts = nullptr; // List of all running scripts friend class DLevelScript; friend class FBehavior; friend struct FBehaviorContainer; }; #endif //__P_ACS_H__