diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /vstdlib | |
| download | archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.tar.xz archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.zip | |
Diffstat (limited to 'vstdlib')
| -rw-r--r-- | vstdlib/KeyValuesSystem.cpp | 416 | ||||
| -rw-r--r-- | vstdlib/coroutine.cpp | 1157 | ||||
| -rw-r--r-- | vstdlib/coroutine_osx.vpc | 31 | ||||
| -rw-r--r-- | vstdlib/coroutine_win64.masm | 175 | ||||
| -rw-r--r-- | vstdlib/cvar.cpp | 899 | ||||
| -rw-r--r-- | vstdlib/getstackptr64.masm | 17 | ||||
| -rw-r--r-- | vstdlib/jobthread.cpp | 1457 | ||||
| -rw-r--r-- | vstdlib/osversion.cpp | 427 | ||||
| -rw-r--r-- | vstdlib/processutils.cpp | 473 | ||||
| -rw-r--r-- | vstdlib/random.cpp | 265 | ||||
| -rw-r--r-- | vstdlib/vcover.cpp | 9 | ||||
| -rw-r--r-- | vstdlib/vstdlib.vpc | 157 | ||||
| -rw-r--r-- | vstdlib/vstdlib_exclude.vpc | 20 | ||||
| -rw-r--r-- | vstdlib/xbox/___FirstModule.cpp | 19 |
14 files changed, 5522 insertions, 0 deletions
diff --git a/vstdlib/KeyValuesSystem.cpp b/vstdlib/KeyValuesSystem.cpp new file mode 100644 index 0000000..0665d94 --- /dev/null +++ b/vstdlib/KeyValuesSystem.cpp @@ -0,0 +1,416 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include <vstdlib/IKeyValuesSystem.h> +#include <KeyValues.h> +#include "mempool.h" +#include "utlsymbol.h" +#include "tier0/threadtools.h" +#include "tier1/memstack.h" +#include "tier1/utlmap.h" +#include "tier1/utlstring.h" +#include "tier1/fmtstr.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +#ifdef NO_SBH // no need to pool if using tier0 small block heap +#define KEYVALUES_USE_POOL 1 +#endif + +//----------------------------------------------------------------------------- +// Purpose: Central storage point for KeyValues memory and symbols +//----------------------------------------------------------------------------- +class CKeyValuesSystem : public IKeyValuesSystem +{ +public: + CKeyValuesSystem(); + ~CKeyValuesSystem(); + + // registers the size of the KeyValues in the specified instance + // so it can build a properly sized memory pool for the KeyValues objects + // the sizes will usually never differ but this is for versioning safety + void RegisterSizeofKeyValues(int size); + + // allocates/frees a KeyValues object from the shared mempool + void *AllocKeyValuesMemory(int size); + void FreeKeyValuesMemory(void *pMem); + + // symbol table access (used for key names) + HKeySymbol GetSymbolForString( const char *name, bool bCreate ); + const char *GetStringForSymbol(HKeySymbol symbol); + + // returns the wide version of ansi, also does the lookup on #'d strings + void GetLocalizedFromANSI( const char *ansi, wchar_t *outBuf, int unicodeBufferSizeInBytes); + void GetANSIFromLocalized( const wchar_t *wchar, char *outBuf, int ansiBufferSizeInBytes ); + + // for debugging, adds KeyValues record into global list so we can track memory leaks + virtual void AddKeyValuesToMemoryLeakList(void *pMem, HKeySymbol name); + virtual void RemoveKeyValuesFromMemoryLeakList(void *pMem); + + // maintain a cache of KeyValues we load from disk. This saves us quite a lot of time on app startup. + virtual void AddFileKeyValuesToCache( const KeyValues* _kv, const char *resourceName, const char *pathID ); + virtual bool LoadFileKeyValuesFromCache( KeyValues* outKv, const char *resourceName, const char *pathID, IBaseFileSystem *filesystem ) const; + virtual void InvalidateCache(); + virtual void InvalidateCacheForFile( const char *resourceName, const char *pathID ); + +private: +#ifdef KEYVALUES_USE_POOL + CUtlMemoryPool *m_pMemPool; +#endif + int m_iMaxKeyValuesSize; + + // string hash table + CMemoryStack m_Strings; + struct hash_item_t + { + int stringIndex; + hash_item_t *next; + }; + CUtlMemoryPool m_HashItemMemPool; + CUtlVector<hash_item_t> m_HashTable; + int CaseInsensitiveHash(const char *string, int iBounds); + + void DoInvalidateCache(); + + struct MemoryLeakTracker_t + { + int nameIndex; + void *pMem; + }; + static bool MemoryLeakTrackerLessFunc( const MemoryLeakTracker_t &lhs, const MemoryLeakTracker_t &rhs ) + { + return lhs.pMem < rhs.pMem; + } + CUtlRBTree<MemoryLeakTracker_t, int> m_KeyValuesTrackingList; + + CThreadFastMutex m_mutex; + + CUtlMap<CUtlString, KeyValues*> m_KeyValueCache; +}; + +// EXPOSE_SINGLE_INTERFACE(CKeyValuesSystem, IKeyValuesSystem, KEYVALUES_INTERFACE_VERSION); + +//----------------------------------------------------------------------------- +// Instance singleton and expose interface to rest of code +//----------------------------------------------------------------------------- +static CKeyValuesSystem g_KeyValuesSystem; + +IKeyValuesSystem *KeyValuesSystem() +{ + return &g_KeyValuesSystem; +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CKeyValuesSystem::CKeyValuesSystem() +: m_HashItemMemPool(sizeof(hash_item_t), 64, UTLMEMORYPOOL_GROW_FAST, "CKeyValuesSystem::m_HashItemMemPool") +, m_KeyValuesTrackingList(0, 0, MemoryLeakTrackerLessFunc) +, m_KeyValueCache( UtlStringLessFunc ) +{ + // initialize hash table + m_HashTable.AddMultipleToTail(2047); + for (int i = 0; i < m_HashTable.Count(); i++) + { + m_HashTable[i].stringIndex = 0; + m_HashTable[i].next = NULL; + } + + m_Strings.Init( 4*1024*1024, 64*1024, 0, 4 ); + char *pszEmpty = ((char *)m_Strings.Alloc(1)); + *pszEmpty = 0; + +#ifdef KEYVALUES_USE_POOL + m_pMemPool = NULL; +#endif + m_iMaxKeyValuesSize = sizeof(KeyValues); +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +CKeyValuesSystem::~CKeyValuesSystem() +{ +#ifdef KEYVALUES_USE_POOL +#ifdef _DEBUG + // display any memory leaks + if (m_pMemPool && m_pMemPool->Count() > 0) + { + DevMsg("Leaked KeyValues blocks: %d\n", m_pMemPool->Count()); + } + + // iterate all the existing keyvalues displaying their names + for (int i = 0; i < m_KeyValuesTrackingList.MaxElement(); i++) + { + if (m_KeyValuesTrackingList.IsValidIndex(i)) + { + DevMsg("\tleaked KeyValues(%s)\n", &m_Strings[m_KeyValuesTrackingList[i].nameIndex]); + } + } +#endif + + delete m_pMemPool; +#endif + + DoInvalidateCache(); +} + +//----------------------------------------------------------------------------- +// Purpose: registers the size of the KeyValues in the specified instance +// so it can build a properly sized memory pool for the KeyValues objects +// the sizes will usually never differ but this is for versioning safety +//----------------------------------------------------------------------------- +void CKeyValuesSystem::RegisterSizeofKeyValues(int size) +{ + if (size > m_iMaxKeyValuesSize) + { + m_iMaxKeyValuesSize = size; + } +} + +#ifdef KEYVALUES_USE_POOL +static void KVLeak( char const *fmt, ... ) +{ + va_list argptr; + char data[1024]; + + va_start(argptr, fmt); + Q_vsnprintf(data, sizeof( data ), fmt, argptr); + va_end(argptr); + + Msg( "%s", data ); +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: allocates a KeyValues object from the shared mempool +//----------------------------------------------------------------------------- +void *CKeyValuesSystem::AllocKeyValuesMemory(int size) +{ +#ifdef KEYVALUES_USE_POOL + // allocate, if we don't have one yet + if (!m_pMemPool) + { + m_pMemPool = new CUtlMemoryPool(m_iMaxKeyValuesSize, 1024, UTLMEMORYPOOL_GROW_FAST, "CKeyValuesSystem::m_pMemPool" ); + m_pMemPool->SetErrorReportFunc( KVLeak ); + } + + return m_pMemPool->Alloc(size); +#else + return malloc( size ); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: frees a KeyValues object from the shared mempool +//----------------------------------------------------------------------------- +void CKeyValuesSystem::FreeKeyValuesMemory(void *pMem) +{ +#ifdef KEYVALUES_USE_POOL + m_pMemPool->Free(pMem); +#else + free( pMem ); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: symbol table access (used for key names) +//----------------------------------------------------------------------------- +HKeySymbol CKeyValuesSystem::GetSymbolForString( const char *name, bool bCreate ) +{ + if ( !name ) + { + return (-1); + } + + AUTO_LOCK( m_mutex ); + + int hash = CaseInsensitiveHash(name, m_HashTable.Count()); + int i = 0; + hash_item_t *item = &m_HashTable[hash]; + while (1) + { + if (!stricmp(name, (char *)m_Strings.GetBase() + item->stringIndex )) + { + return (HKeySymbol)item->stringIndex; + } + + i++; + + if (item->next == NULL) + { + if ( !bCreate ) + { + // not found + return -1; + } + + // we're not in the table + if (item->stringIndex != 0) + { + // first item is used, an new item + item->next = (hash_item_t *)m_HashItemMemPool.Alloc(sizeof(hash_item_t)); + item = item->next; + } + + // build up the new item + item->next = NULL; + char *pString = (char *)m_Strings.Alloc( V_strlen(name) + 1 ); + if ( !pString ) + { + Error( "Out of keyvalue string space" ); + return -1; + } + item->stringIndex = pString - (char *)m_Strings.GetBase(); + strcpy(pString, name); + return (HKeySymbol)item->stringIndex; + } + + item = item->next; + } + + // shouldn't be able to get here + Assert(0); + return (-1); +} + +//----------------------------------------------------------------------------- +// Purpose: symbol table access +//----------------------------------------------------------------------------- +const char *CKeyValuesSystem::GetStringForSymbol(HKeySymbol symbol) +{ + if ( symbol == -1 ) + { + return ""; + } + return ((char *)m_Strings.GetBase() + (size_t)symbol); +} + +//----------------------------------------------------------------------------- +// Purpose: adds KeyValues record into global list so we can track memory leaks +//----------------------------------------------------------------------------- +void CKeyValuesSystem::AddKeyValuesToMemoryLeakList(void *pMem, HKeySymbol name) +{ +#ifdef _DEBUG + // only track the memory leaks in debug builds + MemoryLeakTracker_t item = { name, pMem }; + m_KeyValuesTrackingList.Insert(item); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: used to track memory leaks +//----------------------------------------------------------------------------- +void CKeyValuesSystem::RemoveKeyValuesFromMemoryLeakList(void *pMem) +{ +#ifdef _DEBUG + // only track the memory leaks in debug builds + MemoryLeakTracker_t item = { 0, pMem }; + int index = m_KeyValuesTrackingList.Find(item); + m_KeyValuesTrackingList.RemoveAt(index); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Removes a particular key value file (from a particular source) from the cache if present. +//----------------------------------------------------------------------------- +void CKeyValuesSystem::InvalidateCacheForFile(const char *resourceName, const char *pathID) +{ + CUtlString identString( CFmtStr( "%s::%s", resourceName ? resourceName : "", pathID ? pathID : "" ) ); + + CUtlMap<CUtlString, KeyValues*>::IndexType_t index = m_KeyValueCache.Find( identString ); + if ( m_KeyValueCache.IsValidIndex( index ) ) + { + m_KeyValueCache[ index ]->deleteThis(); + m_KeyValueCache.RemoveAt( index ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Adds a particular key value file (from a particular source) to the cache if not already present. +//----------------------------------------------------------------------------- +void CKeyValuesSystem::AddFileKeyValuesToCache(const KeyValues* _kv, const char *resourceName, const char *pathID) +{ + CUtlString identString( CFmtStr( "%s::%s", resourceName ? resourceName : "", pathID ? pathID : "" ) ); + // Some files actually have multiple roots, and if you use regular MakeCopy (without passing true), those + // will be missed. This caused a bug in soundscapes on dedicated servers. + m_KeyValueCache.Insert( identString, _kv->MakeCopy( true ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: Fetches a particular keyvalue from the cache, and copies into _outKv (clearing anything there already). +//----------------------------------------------------------------------------- +bool CKeyValuesSystem::LoadFileKeyValuesFromCache(KeyValues* outKv, const char *resourceName, const char *pathID, IBaseFileSystem *filesystem) const +{ + Assert( outKv ); + Assert( resourceName ); + + COM_TimestampedLog("CKeyValuesSystem::LoadFileKeyValuesFromCache(%s%s%s): Begin", pathID ? pathID : "", pathID && resourceName ? "/" : "", resourceName ? resourceName : ""); + + CUtlString identString(CFmtStr("%s::%s", resourceName ? resourceName : "", pathID ? pathID : "")); + + CUtlMap<CUtlString, KeyValues*>::IndexType_t index = m_KeyValueCache.Find( identString ); + + if ( m_KeyValueCache.IsValidIndex( index ) ) { + (*outKv) = ( *m_KeyValueCache[ index ] ); + COM_TimestampedLog("CKeyValuesSystem::LoadFileKeyValuesFromCache(%s%s%s): End / Hit", pathID ? pathID : "", pathID && resourceName ? "/" : "", resourceName ? resourceName : ""); + return true; + } + + COM_TimestampedLog("CKeyValuesSystem::LoadFileKeyValuesFromCache(%s%s%s): End / Miss", pathID ? pathID : "", pathID && resourceName ? "/" : "", resourceName ? resourceName : ""); + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Evicts everything from the cache, cleans up the memory used. +//----------------------------------------------------------------------------- +void CKeyValuesSystem::InvalidateCache() +{ + DoInvalidateCache(); +} + +//----------------------------------------------------------------------------- +// Purpose: generates a simple hash value for a string +//----------------------------------------------------------------------------- +int CKeyValuesSystem::CaseInsensitiveHash(const char *string, int iBounds) +{ + unsigned int hash = 0; + + for ( ; *string != 0; string++ ) + { + if (*string >= 'A' && *string <= 'Z') + { + hash = (hash << 1) + (*string - 'A' + 'a'); + } + else + { + hash = (hash << 1) + *string; + } + } + + return hash % iBounds; +} + +//----------------------------------------------------------------------------- +// Purpose: Evicts everything from the cache, cleans up the memory used. +//----------------------------------------------------------------------------- +void CKeyValuesSystem::DoInvalidateCache() +{ + // Cleanup the cache. + FOR_EACH_MAP_FAST( m_KeyValueCache, mapIndex ) + { + m_KeyValueCache[mapIndex]->deleteThis(); + } + + // Apparently you cannot call RemoveAll on a map without also purging the contents because... ? + // If you do and you continue to use the map, you will eventually wind up in a case where you + // have an empty map but it still iterates over elements. Awesome? + m_KeyValueCache.Purge(); +} + diff --git a/vstdlib/coroutine.cpp b/vstdlib/coroutine.cpp new file mode 100644 index 0000000..5cabe93 --- /dev/null +++ b/vstdlib/coroutine.cpp @@ -0,0 +1,1157 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// Build Notes: In order for the coroutine system to work a few build options +// need to be set for coroutine.cpp itself. These are the VPC +// entries for those options: +// $Compiler +// { +// $EnableC++Exceptions "No" +// $BasicRuntimeChecks "Default" +// $EnableFloatingPointExceptions "No" +// } +// +// If you have not set these options you will get a strange popup in +// Visual Studio at the end of Coroutine_Continue(). +// +//============================================================================= + +//#include "pch_vstdlib.h" +#if defined(_DEBUG) +// Verify that something is false +#define DbgVerifyNot(x) Assert(!x) +#else +#define DbgVerifyNot(x) x +#endif + +#include "vstdlib/coroutine.h" +#include "tier0/vprof.h" +#include "tier0/minidump.h" +#include "tier1/utllinkedlist.h" +#include "tier1/utlvector.h" +#include <setjmp.h> + +// for debugging +//#define CHECK_STACK_CORRUPTION + + +#ifndef STEAM +#define PvAlloc(x) malloc(x) +#define FreePv(x) free(x) +#endif + +#ifdef CHECK_STACK_CORRUPTION +#include "tier1/checksum_md5.h" +#include "../tier1/checksum_md5.cpp" +#endif // CHECK_STACK_CORRUPTION + +//#define COROUTINE_TRACE +#ifdef COROUTINE_TRACE +#include "tier1/fmtstr.h" +static CFmtStr g_fmtstr; +#ifdef WIN32 +extern "C" __declspec(dllimport) void __stdcall OutputDebugStringA( const char * ); +#else +void OutputDebugStringA( const char *pchMsg ) { fprintf( stderr, pchMsg ); fflush( stderr ); } +#endif +#define CoroutineDbgMsg( fmt, ... ) \ +{ \ + g_fmtstr.sprintf( fmt, ##__VA_ARGS__ ); \ + OutputDebugStringA( g_fmtstr ); \ +} +#else +#define CoroutineDbgMsg( pchMsg, ... ) +#endif // COROUTINE_TRACE + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#if defined( _MSC_VER ) && ( _MSC_VER >= 1900 ) && defined( PLATFORM_64BITS ) +//the VS2105 longjmp() seems to freak out jumping back into a coroutine (just like linux if _FORTIFY_SOURCE is defined) +// I can't find an analogy to _FORTIFY_SOURCE for MSVC at the moment, so I wrote a quick assembly to longjmp() without any safety checks +extern "C" NORETURN void Coroutine_LongJmp_Unchecked( jmp_buf buffer, int nResult ); +#define Coroutine_longjmp Coroutine_LongJmp_Unchecked + +#ifdef _WIN64 +#define Q_offsetof(s,m) (size_t)( (ptrdiff_t)&reinterpret_cast<const volatile char&>((((s *)0)->m)) ) +#else +#define Q_offsetof(s,m) (size_t)&reinterpret_cast<const volatile char&>((((s *)0)->m)) +#endif +#define SIZEOF_MEMBER( className, memberName ) sizeof( ((className*)nullptr)->memberName ) + + +#define Validate_Jump_Buffer( _Member ) COMPILE_TIME_ASSERT( (Q_offsetof( _JUMP_BUFFER, _Member ) == Q_offsetof( _Duplicate_JUMP_BUFFER, _Member )) && (SIZEOF_MEMBER( _JUMP_BUFFER, _Member ) == SIZEOF_MEMBER( _Duplicate_JUMP_BUFFER, _Member )) ) + + //validate that the structure in assembly matches what the crt setjmp thinks it is +# if defined( PLATFORM_64BITS ) + struct _Duplicate_JUMP_BUFFER + { + unsigned __int64 Frame; + unsigned __int64 Rbx; + unsigned __int64 Rsp; + unsigned __int64 Rbp; + unsigned __int64 Rsi; + unsigned __int64 Rdi; + unsigned __int64 R12; + unsigned __int64 R13; + unsigned __int64 R14; + unsigned __int64 R15; + unsigned __int64 Rip; + unsigned long MxCsr; + unsigned short FpCsr; + unsigned short Spare; + + SETJMP_FLOAT128 Xmm6; + SETJMP_FLOAT128 Xmm7; + SETJMP_FLOAT128 Xmm8; + SETJMP_FLOAT128 Xmm9; + SETJMP_FLOAT128 Xmm10; + SETJMP_FLOAT128 Xmm11; + SETJMP_FLOAT128 Xmm12; + SETJMP_FLOAT128 Xmm13; + SETJMP_FLOAT128 Xmm14; + SETJMP_FLOAT128 Xmm15; + }; + + COMPILE_TIME_ASSERT( sizeof( _JUMP_BUFFER ) == sizeof( _Duplicate_JUMP_BUFFER ) ); + Validate_Jump_Buffer( Frame ); + Validate_Jump_Buffer( Rbx ); + Validate_Jump_Buffer( Rsp ); + Validate_Jump_Buffer( Rbp ); + Validate_Jump_Buffer( Rsi ); + Validate_Jump_Buffer( Rdi ); + Validate_Jump_Buffer( R12 ); + Validate_Jump_Buffer( R13 ); + Validate_Jump_Buffer( R14 ); + Validate_Jump_Buffer( R15 ); + Validate_Jump_Buffer( Rip ); + Validate_Jump_Buffer( MxCsr ); + Validate_Jump_Buffer( FpCsr ); + Validate_Jump_Buffer( Spare ); + + Validate_Jump_Buffer( Xmm6 ); + Validate_Jump_Buffer( Xmm7 ); + Validate_Jump_Buffer( Xmm8 ); + Validate_Jump_Buffer( Xmm9 ); + Validate_Jump_Buffer( Xmm10 ); + Validate_Jump_Buffer( Xmm11 ); + Validate_Jump_Buffer( Xmm12 ); + Validate_Jump_Buffer( Xmm13 ); + Validate_Jump_Buffer( Xmm14 ); + Validate_Jump_Buffer( Xmm15 ); +# else + struct _Duplicate_JUMP_BUFFER + { + unsigned long Ebp; + unsigned long Ebx; + unsigned long Edi; + unsigned long Esi; + unsigned long Esp; + unsigned long Eip; + unsigned long Registration; + unsigned long TryLevel; + unsigned long Cookie; + unsigned long UnwindFunc; + unsigned long UnwindData[6]; + }; + + COMPILE_TIME_ASSERT( sizeof( _JUMP_BUFFER ) == sizeof( _Duplicate_JUMP_BUFFER ) ); + + Validate_Jump_Buffer( Ebp ); + Validate_Jump_Buffer( Ebx ); + Validate_Jump_Buffer( Edi ); + Validate_Jump_Buffer( Esi ); + Validate_Jump_Buffer( Esp ); + Validate_Jump_Buffer( Eip ); + Validate_Jump_Buffer( Registration ); + Validate_Jump_Buffer( TryLevel ); + Validate_Jump_Buffer( Cookie ); + Validate_Jump_Buffer( UnwindFunc ); + Validate_Jump_Buffer( UnwindData[6] ); +# endif + +#else +#define Coroutine_longjmp longjmp +#endif + + +// it *feels* like we should need barriers around our setjmp/longjmp calls, and the memcpy's +// to make sure the optimizer doesn't reorder us across register load/stores, so I've put them +// in what seem like the appropriate spots, but we seem to run ok without them, so... +#ifdef GNUC +#define RW_MEMORY_BARRIER /* __sync_synchronize() */ +#else +#define RW_MEMORY_BARRIER /* _ReadWriteBarrier() */ +#endif + + + +// return values from setjmp() +static const int k_iSetJmpStateSaved = 0x00; +static const int k_iSetJmpContinue = 0x01; +static const int k_iSetJmpDone = 0x02; +static const int k_iSetJmpDbgBreak = 0x03; + +// distance up the stack that coroutine functions stacks' start +#ifdef _PS3 +// PS3 has a small stack. Hopefully we dont need 64k of padding! +static const int k_cubCoroutineStackGap = (3 * 1024); +static const int k_cubCoroutineStackGapSmall = 64; +#else +static const int k_cubCoroutineStackGap = (64 * 1024); +static const int k_cubCoroutineStackGapSmall = 64; +#endif + +// Warning size for allocated stacks +#ifdef _DEBUG +// In debug builds, we'll end up with much more stack usage in some scenarios that isn't representative of release +// builds. We should still warn if we're going way above what we could expect the optimizer to save us from, but the +// warning is more salient in release. +static const int k_cubMaxCoroutineStackSize = (48 * 1024); +#else +static const int k_cubMaxCoroutineStackSize = (32 * 1024); +#endif // defined( _DEBUG ) + +#ifdef _WIN64 +extern "C" byte *GetStackPtr64(); +#define GetStackPtr( pStackPtr) byte *pStackPtr = GetStackPtr64(); +#else +#ifdef WIN32 +#define GetStackPtr( pStackPtr ) byte *pStackPtr; __asm mov pStackPtr, esp +#elif defined(GNUC) +// Apple's version of gcc/g++ doesn't return the expected value using the intrinsic, so +// do it the old fashioned way - this will also use asm on linux (since we don't compile +// with llvm/clang there) but that seems fine. +#if defined(__llvm__) || defined(__clang__) +#define GetStackPtr( pStackPtr ) byte *pStackPtr = (byte*)__builtin_frame_address(0) +#else +#define GetStackPtr( pStackPtr ) register byte *pStackPtr __asm__( "esp" ) +#endif +#elif defined(__SNC__) +#define GetStackPtr( pStackPtr ) byte *pStackPtr = (byte*)__builtin_frame_address(0) +#else +#error +#endif +#endif + +#ifdef _M_X64 +#define _REGISTER_ALIGNMENT 16ull + +int CalcAlignOffset( const unsigned char *p ) +{ + return static_cast<int>( AlignValue( p, _REGISTER_ALIGNMENT ) - p ); +} + +#endif + + +//----------------------------------------------------------------------------- +// Purpose: single coroutine descriptor +//----------------------------------------------------------------------------- +#if defined( _PS3 ) && defined( _DEBUG ) +byte rgStackTempBuffer[65535]; +#endif +class CCoroutine +{ +public: + + CCoroutine() + { + m_pSavedStack = NULL; + m_pStackHigh = m_pStackLow = NULL; + m_cubSavedStack = 0; + m_pFunc = NULL; + m_pchName = "(none)"; + m_iJumpCode = 0; + m_pchDebugMsg = NULL; +#ifdef COROUTINE_TRACE + m_hCoroutine = -1; +#endif +#ifdef _M_X64 + m_nAlignmentBytes = CalcAlignOffset( m_rgubRegisters ); +#endif +#if defined( VPROF_ENABLED ) + m_pVProfNodeScope = NULL; +#endif + } + + jmp_buf &GetRegisters() + { +#ifdef _M_X64 + // Did we get moved in memory in such a way that the registers became unaligned? + // If so, fix them up now + size_t align = _REGISTER_ALIGNMENT - 1; + unsigned char *pRegistersCur = &m_rgubRegisters[m_nAlignmentBytes]; + if ( (size_t)pRegistersCur & align ) + { + m_nAlignmentBytes = CalcAlignOffset( m_rgubRegisters ); + unsigned char *pRegistersNew = &m_rgubRegisters[m_nAlignmentBytes]; + Q_memmove( pRegistersNew, pRegistersCur, sizeof(jmp_buf) ); + pRegistersCur = pRegistersNew; + } + + return *reinterpret_cast<jmp_buf *>( pRegistersCur ); +#else + return m_Registers; +#endif + } + + ~CCoroutine() + { + if ( m_pSavedStack ) + { + FreePv( m_pSavedStack ); + } + } + + FORCEINLINE void RestoreStack() + { + if ( m_cubSavedStack ) + { + Assert( m_pStackHigh ); + Assert( m_pSavedStack ); + +#if defined( _PS3 ) && defined( _DEBUG ) + // Our (and Sony's) memory tracking tools may try to walk the stack during a free() call + // if we do the free here at our normal point though the stack is invalid since it's in + // the middle of swapping. Instead move it to a temp buffer now and free while the stack + // frames in place are still ok. + Assert( m_cubSavedStack < Q_ARRAYSIZE( rgStackTempBuffer ) ); + memcpy( &rgStackTempBuffer[0], m_pSavedStack, m_cubSavedStack ); + + FreePv( m_pSavedStack ); + m_pSavedStack = &rgStackTempBuffer[0]; +#endif + + // Assert we're not about to trash our own immediate stack + GetStackPtr( pStack ); + if ( pStack >= m_pStackLow && pStack <= m_pStackHigh ) + { + CoroutineDbgMsg( g_fmtstr.sprintf( "Restoring stack over ESP (%x, %x, %x)\n", pStack, m_pStackLow, m_pStackHigh ) ); + AssertMsg3( false, "Restoring stack over ESP (%p, %p, %p)\n", pStack, m_pStackLow, m_pStackHigh ); + } + + // Make sure we can access the our instance pointer after restoring the stack. This function is inlined, so the compiler could decide to + // use an existing coroutine pointer that is already on the stack from the previous function (does so on the PS3), and will be overwritten + // when we memcpy below. Any allocations here should be ok, as the caller should have advanced the stack past the stack area where the + // new stack will be copied + CCoroutine *pThis = (CCoroutine*)stackalloc( sizeof( CCoroutine* ) ); + pThis = this; + + RW_MEMORY_BARRIER; + memcpy( m_pStackLow, m_pSavedStack, m_cubSavedStack ); + + // WARNING: The stack has been replaced.. do not use previous stack variables or this + +#ifdef CHECK_STACK_CORRUPTION + MD5Init( &pThis->m_md52 ); + MD5Update( &pThis->m_md52, pThis->m_pStackLow, pThis->m_cubSavedStack ); + MD5Final( pThis->m_digest2, &pThis->m_md52 ); + Assert( 0 == Q_memcmp( pThis->m_digest, pThis->m_digest2, MD5_DIGEST_LENGTH ) ); + +#endif + + // free the saved stack info + pThis->m_cubSavedStack = 0; +#if !defined( _PS3 ) || !defined( _DEBUG ) + FreePv( pThis->m_pSavedStack ); +#endif + pThis->m_pSavedStack = NULL; + + // If we were the "main thread", reset our stack pos to zero + if ( NULL == pThis->m_pFunc ) + { + pThis->m_pStackLow = pThis->m_pStackHigh = 0; + } + + // resume accounting against the vprof node we were in when we yielded + // Make sure we are added after the coroutine we just copied onto the stack +#if defined( VPROF_ENABLED ) + pThis->m_pVProfNodeScope = g_VProfCurrentProfile.GetCurrentNode(); + + if ( g_VProfCurrentProfile.IsEnabled() ) + { + FOR_EACH_VEC_BACK( pThis->m_vecProfNodeStack, i ) + { + g_VProfCurrentProfile.EnterScope( + pThis->m_vecProfNodeStack[i]->GetName(), + 0, + g_VProfCurrentProfile.GetBudgetGroupName( pThis->m_vecProfNodeStack[i]->GetBudgetGroupID() ), + false, + g_VProfCurrentProfile.GetBudgetGroupFlags( pThis->m_vecProfNodeStack[i]->GetBudgetGroupID() ) + ); + } + } + + pThis->m_vecProfNodeStack.Purge(); +#endif + } + } + + FORCEINLINE void SaveStack() + { + MEM_ALLOC_CREDIT_( "Coroutine saved stack" ); + if ( m_pSavedStack ) + { + FreePv( m_pSavedStack ); + } + + + GetStackPtr( pLocal ); + + m_pStackLow = pLocal; + m_cubSavedStack = (m_pStackHigh - m_pStackLow); + m_pSavedStack = (byte *)PvAlloc( m_cubSavedStack ); + + // if you hit this assert, it's because you're allocating way too much stuff on the stack in your job + // check you haven't got any overly large string buffers allocated on the stack + Assert( m_cubSavedStack < k_cubMaxCoroutineStackSize ); + +#if defined( VPROF_ENABLED ) + // Exit any current vprof scope when we yield, and remember the vprof stack so we can restore it when we run again + m_vecProfNodeStack.RemoveAll(); + + CVProfNode *pCurNode = g_VProfCurrentProfile.GetCurrentNode(); + while ( pCurNode && m_pVProfNodeScope && pCurNode != m_pVProfNodeScope && pCurNode != g_VProfCurrentProfile.GetRoot() ) + { + m_vecProfNodeStack.AddToTail( pCurNode ); + g_VProfCurrentProfile.ExitScope(); + pCurNode = g_VProfCurrentProfile.GetCurrentNode(); + } + + m_pVProfNodeScope = NULL; +#endif + + RW_MEMORY_BARRIER; + // save the stack in the newly allocated slot + memcpy( m_pSavedStack, m_pStackLow, m_cubSavedStack ); + +#ifdef CHECK_STACK_CORRUPTION + MD5Init( &m_md5 ); + MD5Update( &m_md5, m_pSavedStack, m_cubSavedStack ); + MD5Final( m_digest, &m_md5 ); +#endif + } + +#ifdef DBGFLAG_VALIDATE + void Validate( CValidator &validator, const char *pchName ) + { + validator.Push( "CCoroutine", this, pchName ); + validator.ClaimMemory( m_pSavedStack ); + validator.Pop(); + } +#endif + +#ifdef _M_X64 + unsigned char m_rgubRegisters[sizeof(jmp_buf) + _REGISTER_ALIGNMENT]; + int m_nAlignmentBytes; +#else + jmp_buf m_Registers; +#endif + + byte *m_pStackHigh; // position of initial entry to the coroutine (stack ptr before continue is ran) + byte *m_pStackLow; // low point on the stack we plan on saving (stack ptr when we yield) + byte *m_pSavedStack; // pointer to the saved stack (allocated on heap) + int m_cubSavedStack; // amount of data on stack + const char *m_pchName; + int m_iJumpCode; + const char *m_pchDebugMsg; + +#ifdef COROUTINE_TRACE + HCoroutine m_hCoroutine; // for debugging +#endif + + CoroutineFunc_t m_pFunc; + void *m_pvParam; +#if defined( VPROF_ENABLED ) + CUtlVector<CVProfNode *> m_vecProfNodeStack; + CVProfNode *m_pVProfNodeScope; +#endif + +#ifdef CHECK_STACK_CORRUPTION + MD5Context_t m_md5; + unsigned char m_digest[MD5_DIGEST_LENGTH]; + MD5Context_t m_md52; + unsigned char m_digest2[MD5_DIGEST_LENGTH]; +#endif +}; + +//----------------------------------------------------------------------------- +// Purpose: manages list of all coroutines +//----------------------------------------------------------------------------- +class CCoroutineMgr +{ +public: + CCoroutineMgr() + { + m_topofexceptionchain = 0; + + // reserve the 0 index as the main coroutine + HCoroutine hMainCoroutine = m_ListCoroutines.AddToTail(); + + m_ListCoroutines[hMainCoroutine].m_pchName = "(main)"; +#ifdef COROUTINE_TRACE + m_ListCoroutines[hMainCoroutine].m_hCoroutine = hMainCoroutine; +#endif + + // mark it as currently running + m_VecCoroutineStack.AddToTail( hMainCoroutine ); + } + + HCoroutine CreateCoroutine( CoroutineFunc_t pFunc, void *pvParam ) + { + HCoroutine hCoroutine = m_ListCoroutines.AddToTail(); + + CoroutineDbgMsg( g_fmtstr.sprintf( "Coroutine_Create() hCoroutine = %x pFunc = 0x%x pvParam = 0x%x\n", hCoroutine, pFunc, pvParam ) ); + + m_ListCoroutines[hCoroutine].m_pFunc = pFunc; + m_ListCoroutines[hCoroutine].m_pvParam = pvParam; + m_ListCoroutines[hCoroutine].m_pSavedStack = NULL; + m_ListCoroutines[hCoroutine].m_cubSavedStack = 0; + m_ListCoroutines[hCoroutine].m_pStackHigh = m_ListCoroutines[hCoroutine].m_pStackLow = NULL; + m_ListCoroutines[hCoroutine].m_pchName = "(no name set)"; +#ifdef COROUTINE_TRACE + m_ListCoroutines[hCoroutine].m_hCoroutine = hCoroutine; +#endif + + return hCoroutine; + } + + HCoroutine GetActiveCoroutineHandle() + { + // look up the coroutine of the last item on the stack + return m_VecCoroutineStack[m_VecCoroutineStack.Count() - 1]; + } + + CCoroutine &GetActiveCoroutine() + { + // look up the coroutine of the last item on the stack + return m_ListCoroutines[GetActiveCoroutineHandle()]; + } + + CCoroutine &GetPreviouslyActiveCoroutine() + { + // look up the coroutine that ran the current coroutine + return m_ListCoroutines[m_VecCoroutineStack[m_VecCoroutineStack.Count() - 2]]; + } + + bool IsValidCoroutine( HCoroutine hCoroutine ) + { + return m_ListCoroutines.IsValidIndex( hCoroutine ) && hCoroutine > 0; + } + + void SetActiveCoroutine( HCoroutine hCoroutine ) + { + m_VecCoroutineStack.AddToTail( hCoroutine ); + } + + void PopCoroutineStack() + { + Assert( m_VecCoroutineStack.Count() > 1 ); + m_VecCoroutineStack.Remove( m_VecCoroutineStack.Count() - 1 ); + } + + bool IsAnyCoroutineActive() + { + return m_VecCoroutineStack.Count() > 1; + } + + void DeleteCoroutine( HCoroutine hCoroutine ) + { + m_ListCoroutines.Remove( hCoroutine ); + } + +#ifdef DBGFLAG_VALIDATE + void Validate( CValidator &validator, const char *pchName ) + { + validator.Push( "CCoroutineMgr", this, pchName ); + + ValidateObj( m_ListCoroutines ); + FOR_EACH_LL( m_ListCoroutines, iRoutine ) + { + ValidateObj( m_ListCoroutines[iRoutine] ); + } + ValidateObj( m_VecCoroutineStack ); + + validator.Pop(); + } +#endif // DBGFLAG_VALIDATE + + uint32 m_topofexceptionchain; + +private: + CUtlLinkedList<CCoroutine, HCoroutine> m_ListCoroutines; + CUtlVector<HCoroutine> m_VecCoroutineStack; +}; + +CThreadLocalPtr< CCoroutineMgr > g_ThreadLocalCoroutineMgr; + +CUtlVector< CCoroutineMgr * > g_VecPCoroutineMgr; +CThreadMutex g_ThreadMutexCoroutineMgr; + +CCoroutineMgr &GCoroutineMgr() +{ + if ( !g_ThreadLocalCoroutineMgr ) + { + AUTO_LOCK( g_ThreadMutexCoroutineMgr ); + g_ThreadLocalCoroutineMgr = new CCoroutineMgr(); + g_VecPCoroutineMgr.AddToTail( g_ThreadLocalCoroutineMgr ); + } + + return *g_ThreadLocalCoroutineMgr; +} + + +//----------------------------------------------------------------------------- +// Purpose: call when a thread is quiting to release any per-thread memory +//----------------------------------------------------------------------------- +void Coroutine_ReleaseThreadMemory() +{ + AUTO_LOCK( g_ThreadMutexCoroutineMgr ); + + if ( g_ThreadLocalCoroutineMgr != NULL ) + { + int iCoroutineMgr = g_VecPCoroutineMgr.Find( g_ThreadLocalCoroutineMgr ); + delete g_VecPCoroutineMgr[iCoroutineMgr]; + g_VecPCoroutineMgr.Remove( iCoroutineMgr ); + } +} + + +// predecs +void Coroutine_Launch( CCoroutine &coroutine ); +void Coroutine_Finish(); + + +//----------------------------------------------------------------------------- +// Purpose: Creates a soroutine, specified by the function, returns a handle +//----------------------------------------------------------------------------- +HCoroutine Coroutine_Create( CoroutineFunc_t pFunc, void *pvParam ) +{ + return GCoroutineMgr().CreateCoroutine( pFunc, pvParam ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Continues a current coroutine +// input: hCoroutine - the coroutine to continue +// pchDebugMsg - if non-NULL, it will generate an assertion in +// that coroutine, then that coroutine will +// immediately yield back to this thread +//----------------------------------------------------------------------------- +static const char *k_pchDebugMsg_GenericBreak = (const char *)1; + +bool Internal_Coroutine_Continue( HCoroutine hCoroutine, const char *pchDebugMsg, const char *pchName ) +{ + Assert( GCoroutineMgr().IsValidCoroutine(hCoroutine) ); + + bool bInCoroutineAlready = GCoroutineMgr().IsAnyCoroutineActive(); + +#ifdef _WIN32 +#ifndef _WIN64 + // make sure nobody has a try/catch block and then yielded + // because we hate that and we will crash + uint32 topofexceptionchain; + __asm mov eax, dword ptr fs:[0] + __asm mov topofexceptionchain, eax + if ( GCoroutineMgr().m_topofexceptionchain == 0 ) + GCoroutineMgr().m_topofexceptionchain = topofexceptionchain; + else + { + Assert( topofexceptionchain == GCoroutineMgr().m_topofexceptionchain ); + } +#endif +#endif + + // start the new coroutine + GCoroutineMgr().SetActiveCoroutine( hCoroutine ); + + CCoroutine &coroutinePrev = GCoroutineMgr().GetPreviouslyActiveCoroutine(); + CCoroutine &coroutine = GCoroutineMgr().GetActiveCoroutine(); + if ( pchName ) + coroutine.m_pchName = pchName; + + CoroutineDbgMsg( g_fmtstr.sprintf( "Coroutine_Continue() %s#%x -> %s#%x\n", coroutinePrev.m_pchName, coroutinePrev.m_hCoroutine, coroutine.m_pchName, coroutine.m_hCoroutine ) ); + + bool bStillRunning = true; + + // set the point for the coroutine to jump back to + RW_MEMORY_BARRIER; + int iResult = setjmp( coroutinePrev.GetRegisters() ); + if ( iResult == k_iSetJmpStateSaved ) + { + // copy the new stack in place + if ( coroutine.m_pSavedStack ) + { + // save any of the main stack that overlaps where the coroutine stack is going to go + GetStackPtr( pStackSavePoint ); + if ( pStackSavePoint <= coroutine.m_pStackHigh ) + { + // save the main stack from where the coroutine stack wishes to start + // if the previous coroutine already had a stack save point, just save + // the whole thing. + if ( NULL == coroutinePrev.m_pStackHigh ) + { + coroutinePrev.m_pStackHigh = coroutine.m_pStackHigh; + } + else + { + Assert( coroutine.m_pStackHigh <= coroutinePrev.m_pStackHigh ); + } + coroutinePrev.SaveStack(); + CoroutineDbgMsg( g_fmtstr.sprintf( "SaveStack() %s#%x [%x - %x]\n", coroutinePrev.m_pchName, coroutinePrev.m_hCoroutine, coroutinePrev.m_pStackLow, coroutinePrev.m_pStackHigh ) ); + } + + // If the coroutine's stack is close enough to where we are on the stack, we need to push ourselves + // down past it, so that the memcpy() doesn't screw up the RestoreStack->memcpy call chain. + if ( coroutine.m_pStackHigh > ( pStackSavePoint - 2048 ) ) + { + // If the entire CR stack is above us, we don't need to pad ourselves. + if ( coroutine.m_pStackLow < pStackSavePoint ) + { + // push ourselves down + int cubPush = pStackSavePoint - coroutine.m_pStackLow + 512; + volatile byte *pvStackGap = (byte*)stackalloc( cubPush ); + pvStackGap[ cubPush-1 ] = 0xF; + CoroutineDbgMsg( g_fmtstr.sprintf( "Adjusting stack point by %d (%x <- %x)\n", cubPush, pvStackGap, &pvStackGap[cubPush] ) ); + } + } + + // This needs to go right here - after we've maybe padded the stack (so that iJumpCode does not + // get stepped on) and before the RestoreStack() call (because that might step on pchDebugMsg!). + if ( pchDebugMsg == NULL ) + { + coroutine.m_iJumpCode = k_iSetJmpContinue; + coroutine.m_pchDebugMsg = NULL; + } + else if ( pchDebugMsg == k_pchDebugMsg_GenericBreak ) + { + coroutine.m_iJumpCode = k_iSetJmpDbgBreak; + coroutine.m_pchDebugMsg = NULL; + } + else + { + coroutine.m_iJumpCode = k_iSetJmpDbgBreak; + coroutine.m_pchDebugMsg = pchDebugMsg; + } + + // restore the coroutine stack + CoroutineDbgMsg( g_fmtstr.sprintf( "RestoreStack() %s#%x [%x - %x] (current %x)\n", coroutine.m_pchName, coroutine.m_hCoroutine, coroutine.m_pStackLow, coroutine.m_pStackHigh, pStackSavePoint ) ); + coroutine.RestoreStack(); + + // the new stack is in place, so no code here can reference local stack vars + // move the program counter + RW_MEMORY_BARRIER; + Coroutine_longjmp( GCoroutineMgr().GetActiveCoroutine().GetRegisters(), GCoroutineMgr().GetActiveCoroutine().m_iJumpCode ); + } + else + { + + // set the stack pos for the new coroutine + // jump a long way forward on the stack + // this needs to be a stackalloc() instead of a static buffer, so it won't get optimized out in release build + int cubGap = bInCoroutineAlready ? k_cubCoroutineStackGapSmall : k_cubCoroutineStackGap; + volatile byte *pvStackGap = (byte*)stackalloc( cubGap ); + pvStackGap[ cubGap-1 ] = 0xF; + + // hasn't started yet, so launch + Coroutine_Launch( coroutine ); + } + + // when the job yields, the above setjmp() will be called again with non-zero value + // code here will never run + } + else if ( iResult == k_iSetJmpContinue ) + { + // just pass through + } + else if ( iResult == k_iSetJmpDone ) + { + // we're done, remove the coroutine + GCoroutineMgr().DeleteCoroutine( Coroutine_GetCurrentlyActive() ); + bStillRunning = false; + } + + // job has suspended itself, we'll get back to it later + GCoroutineMgr().PopCoroutineStack(); + return bStillRunning; +} + + +//----------------------------------------------------------------------------- +// Purpose: Continues a current coroutine +//----------------------------------------------------------------------------- +bool Coroutine_Continue( HCoroutine hCoroutine, const char *pchName ) +{ + return Internal_Coroutine_Continue( hCoroutine, NULL, pchName ); +} + + +//----------------------------------------------------------------------------- +// Purpose: launches a coroutine way ahead on the stack +//----------------------------------------------------------------------------- +void NOINLINE Coroutine_Launch( CCoroutine &coroutine ) +{ +#if defined( VPROF_ENABLED ) + coroutine.m_pVProfNodeScope = g_VProfCurrentProfile.GetCurrentNode(); +#endif + + // set our marker +#ifndef _PS3 + GetStackPtr( pEsp ); +#else + // The stack pointer for the current stack frame points to the top of the stack which already includes space for the + // ABI linkage area. We need to include this area as part of our coroutine stack, as the calling function will copy + // the link register (return address to this function) into this area after calling m_pFunc below. Failing to do so + // could result in the coroutine to return to garbage when complete + uint64 *pStackFrameTwoUp = (uint64*)__builtin_frame_address(2); + + // Need to terminate the stack frame sequence so if someone tries to walk the stack in a co-routine they don't go forever. + *pStackFrameTwoUp = 0; + + // Need to track where we we save up to on yield, add a few bytes so we save just the beginning linkage area of the stack frame + // we added the null termination to. + byte * pEsp = ((byte*)pStackFrameTwoUp)+32; + +#endif + #ifdef _WIN64 + // Add a little extra padding, to capture the spill space for the registers + // that is required for us to reserve ABOVE the return address), and also + // align the stack + coroutine.m_pStackHigh = (byte *)( ((uintptr_t)pEsp + 32 + 15) & ~(uintptr_t)15 ); + + // On Win64, we need to be able to find an exception handler + // if we walk the stack to this point. Currently, + // this is as close to the root as we can go. If we + // try to go higher, we wil fail. That's actually + // OK at run time, because Coroutine_Finish doesn't + // return! + CatchAndWriteMiniDumpForVoidPtrFn( coroutine.m_pFunc, coroutine.m_pvParam, /*bExitQuietly*/ true ); + #else + coroutine.m_pStackHigh = (byte *)pEsp; + + // run the function directly + coroutine.m_pFunc( coroutine.m_pvParam ); + #endif + + // longjmp back to the main 'thread' + Coroutine_Finish(); +} + + +//----------------------------------------------------------------------------- +// Purpose: cancels a currently running coroutine +//----------------------------------------------------------------------------- +void Coroutine_Cancel( HCoroutine hCoroutine ) +{ + GCoroutineMgr().DeleteCoroutine( hCoroutine ); +} + +//----------------------------------------------------------------------------- +// Purpose: cause a debug break in the specified coroutine +//----------------------------------------------------------------------------- +void Coroutine_DebugBreak( HCoroutine hCoroutine ) +{ + Internal_Coroutine_Continue( hCoroutine, k_pchDebugMsg_GenericBreak, NULL ); +} + +//----------------------------------------------------------------------------- +// Purpose: generate an assert (perhaps generating a minidump), with the +// specified failure message, in the specified coroutine +//----------------------------------------------------------------------------- +void Coroutine_DebugAssert( HCoroutine hCoroutine, const char *pchMsg ) +{ + Assert( pchMsg ); + Internal_Coroutine_Continue( hCoroutine, pchMsg, NULL ); +} + +//----------------------------------------------------------------------------- +// Purpose: returns true if the code is currently running inside of a coroutine +//----------------------------------------------------------------------------- +bool Coroutine_IsActive() +{ + return GCoroutineMgr().IsAnyCoroutineActive(); +} + + +//----------------------------------------------------------------------------- +// Purpose: returns a handle the currently active coroutine +//----------------------------------------------------------------------------- +HCoroutine Coroutine_GetCurrentlyActive() +{ + Assert( Coroutine_IsActive() ); + return GCoroutineMgr().GetActiveCoroutineHandle(); +} + + +//----------------------------------------------------------------------------- +// Purpose: lets the main thread continue +//----------------------------------------------------------------------------- +void Coroutine_YieldToMain() +{ + // if you've hit this assert, it's because you're calling yield when not in a coroutine + Assert( Coroutine_IsActive() ); + CCoroutine &coroutinePrev = GCoroutineMgr().GetPreviouslyActiveCoroutine(); + CCoroutine &coroutine = GCoroutineMgr().GetActiveCoroutine(); + CoroutineDbgMsg( g_fmtstr.sprintf( "Coroutine_YieldToMain() %s#%x -> %s#%x\n", coroutine.m_pchName, coroutine.m_hCoroutine, coroutinePrev.m_pchName, coroutinePrev.m_hCoroutine ) ); + +#ifdef _WIN32 +#ifndef _WIN64 + // make sure nobody has a try/catch block and then yielded + // because we hate that and we will crash + uint32 topofexceptionchain; + __asm mov eax, dword ptr fs:[0] + __asm mov topofexceptionchain, eax + if ( GCoroutineMgr().m_topofexceptionchain == 0 ) + GCoroutineMgr().m_topofexceptionchain = topofexceptionchain; + else + { + Assert( topofexceptionchain == GCoroutineMgr().m_topofexceptionchain ); + } +#endif +#endif + + RW_MEMORY_BARRIER; + int iResult = setjmp( coroutine.GetRegisters() ); + if ( ( iResult == k_iSetJmpStateSaved ) || ( iResult == k_iSetJmpDbgBreak ) ) + { + + + // break / assert requested? + if ( iResult == k_iSetJmpDbgBreak ) + { + // Assert (minidump) requested? + if ( coroutine.m_pchDebugMsg ) + { + // Generate a failed assertion + AssertMsg1( !"Coroutine assert requested", "%s", coroutine.m_pchDebugMsg ); + } + else + { + // If we were loaded only to debug, call a break + DebuggerBreakIfDebugging(); + } + + // Now IMMEDIATELY yield back to the main thread + } + + // Clear message, regardless + coroutine.m_pchDebugMsg = NULL; + + // save our stack - all the way to the top, err bottom err, the end of it ( where esp is ) + coroutine.SaveStack(); + CoroutineDbgMsg( g_fmtstr.sprintf( "SaveStack() %s#%x [%x - %x]\n", coroutine.m_pchName, coroutine.m_hCoroutine, coroutine.m_pStackLow, coroutine.m_pStackHigh ) ); + + // restore the main thread stack + // allocate a bunch of stack padding so we don't kill ourselves while in stack restoration + // If the coroutine's stack is close enough to where we are on the stack, we need to push ourselves + // down past it, so that the memcpy() doesn't screw up the RestoreStack->memcpy call chain. + GetStackPtr( pStackPtr ); + if ( pStackPtr >= (coroutinePrev.m_pStackHigh - coroutinePrev.m_cubSavedStack) && ( pStackPtr - 2048 ) <= coroutinePrev.m_pStackHigh ) + { + int cubPush = coroutinePrev.m_cubSavedStack + 512; + volatile byte *pvStackGap = (byte*)stackalloc( cubPush ); + pvStackGap[ cubPush - 1 ] = 0xF; + CoroutineDbgMsg( g_fmtstr.sprintf( "Adjusting stack point by %d (%x <- %x)\n", cubPush, pvStackGap, &pvStackGap[cubPush] ) ); + } + + CoroutineDbgMsg( g_fmtstr.sprintf( "RestoreStack() %s#%x [%x - %x]\n", coroutinePrev.m_pchName, coroutinePrev.m_hCoroutine, coroutinePrev.m_pStackLow, coroutinePrev.m_pStackHigh ) ); + coroutinePrev.RestoreStack(); + + // jump back to the main thread + // Our stack may have been mucked with, can't use local vars anymore! + RW_MEMORY_BARRIER; + Coroutine_longjmp( GCoroutineMgr().GetPreviouslyActiveCoroutine().GetRegisters(), k_iSetJmpContinue ); + + UNREACHABLE(); + } + else + { + // we've been restored, now continue on our merry way + } +} + +//----------------------------------------------------------------------------- +// Purpose: done with the Coroutine, terminate safely +//----------------------------------------------------------------------------- +void Coroutine_Finish() +{ + Assert( Coroutine_IsActive() ); + + CoroutineDbgMsg( g_fmtstr.sprintf( "Coroutine_Finish() %s#%x -> %s#%x\n", GCoroutineMgr().GetActiveCoroutine().m_pchName, GCoroutineMgr().GetActiveCoroutineHandle(), GCoroutineMgr().GetPreviouslyActiveCoroutine().m_pchName, &GCoroutineMgr().GetPreviouslyActiveCoroutine() ) ); + + // allocate a bunch of stack padding so we don't kill ourselves while in stack restoration + volatile byte *pvStackGap = (byte*)stackalloc( GCoroutineMgr().GetPreviouslyActiveCoroutine().m_cubSavedStack + 512 ); + pvStackGap[ GCoroutineMgr().GetPreviouslyActiveCoroutine().m_cubSavedStack + 511 ] = 0xf; + + GCoroutineMgr().GetPreviouslyActiveCoroutine().RestoreStack(); + + RW_MEMORY_BARRIER; + // go back to the main thread, signaling that we're done + Coroutine_longjmp( GCoroutineMgr().GetPreviouslyActiveCoroutine().GetRegisters(), k_iSetJmpDone ); + + UNREACHABLE(); +} + +//----------------------------------------------------------------------------- +// Purpose: Coroutine that spawns another coroutine +//----------------------------------------------------------------------------- +void CoroutineTestFunc( void *pvRelaunch ) +{ + static const char *g_pchTestString = "test string"; + + char rgchT[256]; + Q_strncpy( rgchT, g_pchTestString, sizeof(rgchT) ); + + // yield + Coroutine_YieldToMain(); + + // ensure the string is still valid + DbgVerifyNot( Q_strcmp( rgchT, g_pchTestString ) ); + + if ( !pvRelaunch ) + { + // test launching coroutines inside of coroutines + HCoroutine hCoroutine = Coroutine_Create( &CoroutineTestFunc, (void *)(size_t)0xFFFFFFFF ); + // first pass the coroutines should all still be running + DbgVerify( Coroutine_Continue( hCoroutine, NULL ) ); + // second pass the coroutines should all be finished + DbgVerifyNot( Coroutine_Continue( hCoroutine, NULL ) ); + } +} + + +// test that just spins a few times +void CoroutineTestL2( void * ) +{ + // spin a few times + for ( int i = 0; i < 5; i++ ) + { + Coroutine_YieldToMain(); + } +} + + +// level 1 of a test +void CoroutineTestL1( void *pvecCoroutineL2 ) +{ + CUtlVector<HCoroutine> &vecCoroutineL2 = *(CUtlVector<HCoroutine> *)pvecCoroutineL2; + + int i = 20; + + // launch a set of coroutines + for ( i = 0; i < 20; i++ ) + { + HCoroutine hCoroutine = Coroutine_Create( &CoroutineTestL2, NULL ); + vecCoroutineL2.AddToTail( hCoroutine ); + Coroutine_Continue( hCoroutine, NULL ); + + // now yield back to main occasionally + if ( i % 2 == 1 ) + Coroutine_YieldToMain(); + } + + Assert( i == 20 ); +} + + +//----------------------------------------------------------------------------- +// Purpose: runs a self-test of the coroutine system +// it's working if it doesn't crash +//----------------------------------------------------------------------------- +bool Coroutine_Test() +{ + // basic calling of a coroutine + HCoroutine hCoroutine = Coroutine_Create( &CoroutineTestFunc, NULL ); + Coroutine_Continue( hCoroutine, NULL ); + Coroutine_Continue( hCoroutine, NULL ); + + // now test + CUtlVector<HCoroutine> vecCoroutineL2; + hCoroutine = Coroutine_Create( &CoroutineTestL1, &vecCoroutineL2 ); + Coroutine_Continue( hCoroutine, NULL ); + + // run the sub-coroutines until they're all done + while ( vecCoroutineL2.Count() ) + { + if ( hCoroutine && !Coroutine_Continue( hCoroutine, NULL ) ) + hCoroutine = NULL; + + FOR_EACH_VEC_BACK( vecCoroutineL2, i ) + { + if ( !Coroutine_Continue( vecCoroutineL2[i], NULL ) ) + vecCoroutineL2.Remove( i ); + } + } + + + // new one + hCoroutine = Coroutine_Create( &CoroutineTestFunc, NULL ); + // it has yielded, now continue it's call + { + // pop our stack up so it collides with the coroutine stack position + Coroutine_Continue( hCoroutine, NULL ); + volatile byte *pvAlloca = (byte*)stackalloc( k_cubCoroutineStackGapSmall ); + pvAlloca[ k_cubCoroutineStackGapSmall-1 ] = 0xF; + + Coroutine_Continue( hCoroutine, NULL ); + } + + // now do a whole bunch of them + static const int k_nSimultaneousCoroutines = 10 * 1000; + CUtlVector<HCoroutine> coroutines; + Assert( coroutines.Base() == NULL ); + for (int i = 0; i < k_nSimultaneousCoroutines; i++) + { + coroutines.AddToTail( Coroutine_Create( &CoroutineTestFunc, NULL ) ); + } + + for (int i = 0; i < coroutines.Count(); i++) + { + // first pass the coroutines should all still be running + DbgVerify( Coroutine_Continue( coroutines[i], NULL ) ); + } + + for (int i = 0; i < coroutines.Count(); i++) + { + // second pass the coroutines should all be finished + DbgVerifyNot( Coroutine_Continue( coroutines[i], NULL ) ); + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: returns approximate stack depth of current coroutine. +//----------------------------------------------------------------------------- +size_t Coroutine_GetStackDepth() +{ + // should only get called from a coroutine + Assert( GCoroutineMgr().IsAnyCoroutineActive() ); + if ( !GCoroutineMgr().IsAnyCoroutineActive() ) + return 0; + + GetStackPtr( pLocal ); + CCoroutine &coroutine = GCoroutineMgr().GetActiveCoroutine(); + return ( coroutine.m_pStackHigh - pLocal ); +} + + +//----------------------------------------------------------------------------- +// Purpose: validates memory +//----------------------------------------------------------------------------- +void Coroutine_ValidateGlobals( class CValidator &validator ) +{ +#ifdef DBGFLAG_VALIDATE + AUTO_LOCK( g_ThreadMutexCoroutineMgr ); + + for ( int i = 0; i < g_VecPCoroutineMgr.Count(); i++ ) + { + ValidatePtr( g_VecPCoroutineMgr[i] ); + } + ValidateObj( g_VecPCoroutineMgr ); + +#endif +} diff --git a/vstdlib/coroutine_osx.vpc b/vstdlib/coroutine_osx.vpc new file mode 100644 index 0000000..030b05a --- /dev/null +++ b/vstdlib/coroutine_osx.vpc @@ -0,0 +1,31 @@ +//----------------------------------------------------------------------------- +// COROUTINE_OSX.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR ".." + +$include "$SRCDIR\vpc_scripts\source_lib_base.vpc" + +$Configuration +{ + $Compiler + { + $GCC_ExtraCompilerFlags "-fno-stack-protector" + $PreprocessorDefinitions "$BASE;VSTDLIB_DLL_EXPORT" + } +} + +$Project "coroutine_osx" +{ + $Folder "Source Files" + { + $File "coroutine.cpp" + } + + $Folder "Public Header Files" + { + $File "$SRCDIR\public\vstdlib\coroutine.h" + } +} diff --git a/vstdlib/coroutine_win64.masm b/vstdlib/coroutine_win64.masm new file mode 100644 index 0000000..764ebdd --- /dev/null +++ b/vstdlib/coroutine_win64.masm @@ -0,0 +1,175 @@ +option casemap:none + +.CODE + +; import Coroutine_Finish with its mangled Microsoft Visual C++ name +?Coroutine_Finish@@YAXXZ PROTO + +; extern "C" void SaveNonVolatileRegs( uintptr_t regs[8] ); +; incoming parameter is rcs +SaveNonVolatileRegs PROC FRAME + .endprolog + mov qword ptr[rcx], rbx + mov qword ptr[rcx+8], rbp + mov qword ptr[rcx+16], rsi + mov qword ptr[rcx+24], rdi + mov qword ptr[rcx+32], r12 + mov qword ptr[rcx+40], r13 + mov qword ptr[rcx+48], r14 + mov qword ptr[rcx+56], r15 + ret +SaveNonVolatileRegs ENDP + +; extern "C" void NORETURN Coroutine_Launch_ASM( byte **ppStackHigh, uintptr_t **ppLaunchParentFramePtr, void (*pfnExec)( void* ), void *pvParam ) +; Per Win64 ABI, incoming params are rcx, rdx, r8, r9. initial stack pointer is half-aligned due to return address +Coroutine_Launch_ASM PROC FRAME + ; x64 prolog and prolog description macros: + + ; save caller's nonvolatile registers (pushed in reverse order to match SaveNonVolatileRegs) + ; so that we can slam new values in later to trick the x64 callstack unwind procedure + push r15 + .pushreg r15 + push r14 + .pushreg r14 + push r13 + .pushreg r13 + push r12 + .pushreg r12 + push rdi + .pushreg rdi + push rsi + .pushreg rsi + push rbp + .pushreg rbp + push rbx + .pushreg rbx + + ; stack-allocate Win64 function call shadow space for calls to pfnExec and Coroutine_Finish, + ; plus 8 additional bytes to align the stack frame properly (comes in off by 8) + sub rsp, 28h + .allocstack 28h + + .endprolog + + ; compute top of stack for coroutine: 40 bytes for stack, 64 for saved regs, 8 for return address + ; (we do not bother including the additional unused 32 byte shadow space we own above that) + lea rax, [rsp+70h] + mov qword ptr [rcx], rax + + ; save off the address of our saved regs so that we can memcpy over them later and trick + ; the x64 stack unwind logic into walking up to a different Internal_Coroutine_Continue + lea rax, [rsp+28h] + mov qword ptr [rdx], rax + + ; call pfnExec(pvParam) + mov rcx, r9 + call r8 + + ; call Coroutine_Finish - does not return + call ?Coroutine_Finish@@YAXXZ + +Coroutine_Launch_ASM ENDP + + + + +; Needs to match definition found in setjmp.h +_JUMP_BUFFER STRUCT + m_Frame QWORD ? + m_Rbx QWORD ? + m_Rsp QWORD ? + m_Rbp QWORD ? + m_Rsi QWORD ? + m_Rdi QWORD ? + m_R12 QWORD ? + m_R13 QWORD ? + m_R14 QWORD ? + m_R15 QWORD ? + m_Rip QWORD ? + m_MxCsr DWORD ? + m_FpCsr WORD ? + m_Spare WORD ? + m_Xmm6 XMMWORD ? + m_Xmm7 XMMWORD ? + m_Xmm8 XMMWORD ? + m_Xmm9 XMMWORD ? + m_Xmm10 XMMWORD ? + m_Xmm11 XMMWORD ? + m_Xmm12 XMMWORD ? + m_Xmm13 XMMWORD ? + m_Xmm14 XMMWORD ? + m_Xmm15 XMMWORD ? +_JUMP_BUFFER ENDS + + +;This is the reference asm for __intrinsic_setjmp() in VS2015 +;mov qword ptr [rcx],rdx ; intrinsic call site does "mov rdx,rbp" followed by "add rdx,0FFFFFFFFFFFFFFC0h", looks like a nonstandard abi +;mov qword ptr [rcx+8],rbx +;mov qword ptr [rcx+18h],rbp +;mov qword ptr [rcx+20h],rsi +;mov qword ptr [rcx+28h],rdi +;mov qword ptr [rcx+30h],r12 +;mov qword ptr [rcx+38h],r13 +;mov qword ptr [rcx+40h],r14 +;mov qword ptr [rcx+48h],r15 +;lea r8,[rsp+8] ; rsp set to post-return address +;mov qword ptr [rcx+10h],r8 +;mov r8,qword ptr [rsp] +;mov qword ptr [rcx+50h],r8 +;stmxcsr dword ptr [rcx+58h] +;fnstcw word ptr [rcx+5Ch] +;movdqa xmmword ptr [rcx+60h],xmm6 +;ovdqa xmmword ptr [rcx+70h],xmm7 +;movdqa xmmword ptr [rcx+80h],xmm8 +;movdqa xmmword ptr [rcx+90h],xmm9 +;movdqa xmmword ptr [rcx+0A0h],xmm10 +;movdqa xmmword ptr [rcx+0B0h],xmm11 +;movdqa xmmword ptr [rcx+0C0h],xmm12 +;movdqa xmmword ptr [rcx+0D0h],xmm13 +;movdqa xmmword ptr [rcx+0E0h],xmm14 +;movdqa xmmword ptr [rcx+0F0h],xmm15 +;xor eax,eax +;ret + + +; extern "C" void NORETURN Coroutine_LongJmp_UnChecked( jmp_buf buf, int nResult ) +; Per Win64 ABI, incoming params are rcx, rdx, r8, r9. initial stack pointer is half-aligned due to return address +Coroutine_LongJmp_Unchecked PROC + ;load nResult into result from initial setjmp() + xor rax, rax + mov eax, edx + + ;restore to setjmp() caller state + mov rdx, [rcx]._JUMP_BUFFER.m_Frame ; appears to be an error checking value of (_JUMP_BUFFER.m_Rbp + 0FFFFFFFFFFFFFFC0h) passed non-standardly through rdx to setjmp() + mov rbx, [rcx]._JUMP_BUFFER.m_Rbx + mov rsp, [rcx]._JUMP_BUFFER.m_Rsp + mov rbp, [rcx]._JUMP_BUFFER.m_Rbp + mov rsi, [rcx]._JUMP_BUFFER.m_Rsi + mov rdi, [rcx]._JUMP_BUFFER.m_Rdi + mov r12, [rcx]._JUMP_BUFFER.m_R12 + mov r13, [rcx]._JUMP_BUFFER.m_R13 + mov r14, [rcx]._JUMP_BUFFER.m_R14 + mov r15, [rcx]._JUMP_BUFFER.m_R15 + mov r10, [rcx]._JUMP_BUFFER.m_Rip ; store return address in r10 for return + ldmxcsr [rcx]._JUMP_BUFFER.m_MxCsr + fldcw [rcx]._JUMP_BUFFER.m_FpCsr + ;[rcx]._JUMP_BUFFER.m_Spare + movaps xmm6, [rcx]._JUMP_BUFFER.m_Xmm6 + movaps xmm7, [rcx]._JUMP_BUFFER.m_Xmm7 + movaps xmm8, [rcx]._JUMP_BUFFER.m_Xmm8 + movaps xmm9, [rcx]._JUMP_BUFFER.m_Xmm9 + movaps xmm10, [rcx]._JUMP_BUFFER.m_Xmm10 + movaps xmm11, [rcx]._JUMP_BUFFER.m_Xmm11 + movaps xmm12, [rcx]._JUMP_BUFFER.m_Xmm12 + movaps xmm13, [rcx]._JUMP_BUFFER.m_Xmm13 + movaps xmm14, [rcx]._JUMP_BUFFER.m_Xmm14 + movaps xmm15, [rcx]._JUMP_BUFFER.m_Xmm15 + + ;jmp instead of ret to _JUMP_BUFFER.m_Rip because setjmp() already set the _JUMP_BUFFER.m_Rsp to the post-return state + db 048h ; emit a REX prefix on the jmp to ensure it's a full qword + jmp qword ptr r10 +Coroutine_LongJmp_Unchecked ENDP + + +_TEXT ENDS +END diff --git a/vstdlib/cvar.cpp b/vstdlib/cvar.cpp new file mode 100644 index 0000000..72aca7b --- /dev/null +++ b/vstdlib/cvar.cpp @@ -0,0 +1,899 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//===========================================================================// + +#include "vstdlib/cvar.h" +#include <ctype.h> +#include "tier0/icommandline.h" +#include "tier1/utlrbtree.h" +#include "tier1/strtools.h" +#include "tier1/KeyValues.h" +#include "tier1/convar.h" +#include "tier0/vprof.h" +#include "tier1/tier1.h" +#include "tier1/utlbuffer.h" + +#ifdef _X360 +#include "xbox/xbox_console.h" +#endif + +#ifdef POSIX +#include <wctype.h> +#include <wchar.h> +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// Default implementation of CvarQuery +//----------------------------------------------------------------------------- +class CDefaultCvarQuery : public CBaseAppSystem< ICvarQuery > +{ +public: + virtual void *QueryInterface( const char *pInterfaceName ) + { + if ( !Q_stricmp( pInterfaceName, CVAR_QUERY_INTERFACE_VERSION ) ) + return (ICvarQuery*)this; + return NULL; + + } + + virtual bool AreConVarsLinkable( const ConVar *child, const ConVar *parent ) + { + return true; + } +}; + +static CDefaultCvarQuery s_DefaultCvarQuery; +static ICvarQuery *s_pCVarQuery = NULL; + + +//----------------------------------------------------------------------------- +// Default implementation +//----------------------------------------------------------------------------- +class CCvar : public ICvar +{ +public: + CCvar(); + + // Methods of IAppSystem + virtual bool Connect( CreateInterfaceFn factory ); + virtual void Disconnect(); + virtual void *QueryInterface( const char *pInterfaceName ); + virtual InitReturnVal_t Init(); + virtual void Shutdown(); + + // Inherited from ICVar + virtual CVarDLLIdentifier_t AllocateDLLIdentifier(); + virtual void RegisterConCommand( ConCommandBase *pCommandBase ); + virtual void UnregisterConCommand( ConCommandBase *pCommandBase ); + virtual void UnregisterConCommands( CVarDLLIdentifier_t id ); + virtual const char* GetCommandLineValue( const char *pVariableName ); + virtual ConCommandBase *FindCommandBase( const char *name ); + virtual const ConCommandBase *FindCommandBase( const char *name ) const; + virtual ConVar *FindVar ( const char *var_name ); + virtual const ConVar *FindVar ( const char *var_name ) const; + virtual ConCommand *FindCommand( const char *name ); + virtual const ConCommand *FindCommand( const char *name ) const; + virtual ConCommandBase *GetCommands( void ); + virtual const ConCommandBase *GetCommands( void ) const; + virtual void InstallGlobalChangeCallback( FnChangeCallback_t callback ); + virtual void RemoveGlobalChangeCallback( FnChangeCallback_t callback ); + virtual void CallGlobalChangeCallbacks( ConVar *var, const char *pOldString, float flOldValue ); + virtual void InstallConsoleDisplayFunc( IConsoleDisplayFunc* pDisplayFunc ); + virtual void RemoveConsoleDisplayFunc( IConsoleDisplayFunc* pDisplayFunc ); + virtual void ConsoleColorPrintf( const Color& clr, const char *pFormat, ... ) const; + virtual void ConsolePrintf( const char *pFormat, ... ) const; + virtual void ConsoleDPrintf( const char *pFormat, ... ) const; + virtual void RevertFlaggedConVars( int nFlag ); + virtual void InstallCVarQuery( ICvarQuery *pQuery ); + +#if defined( _X360 ) + virtual void PublishToVXConsole( ); +#endif + + virtual bool IsMaterialThreadSetAllowed( ) const; + virtual void QueueMaterialThreadSetValue( ConVar *pConVar, const char *pValue ); + virtual void QueueMaterialThreadSetValue( ConVar *pConVar, int nValue ); + virtual void QueueMaterialThreadSetValue( ConVar *pConVar, float flValue ); + virtual bool HasQueuedMaterialThreadConVarSets() const; + virtual int ProcessQueuedMaterialThreadConVarSets(); +private: + enum + { + CONSOLE_COLOR_PRINT = 0, + CONSOLE_PRINT, + CONSOLE_DPRINT, + }; + + void DisplayQueuedMessages( ); + + CUtlVector< FnChangeCallback_t > m_GlobalChangeCallbacks; + CUtlVector< IConsoleDisplayFunc* > m_DisplayFuncs; + int m_nNextDLLIdentifier; + ConCommandBase *m_pConCommandList; + + // temporary console area so we can store prints before console display funs are installed + mutable CUtlBuffer m_TempConsoleBuffer; +protected: + + // internals for ICVarIterator + class CCVarIteratorInternal : public ICVarIteratorInternal + { + public: + CCVarIteratorInternal( CCvar *outer ) + : m_pOuter( outer ) + //, m_pHash( &outer->m_CommandHash ), // remember my CCvar, + //m_hashIter( -1, -1 ) // and invalid iterator + , m_pCur( NULL ) + {} + virtual void SetFirst( void ); + virtual void Next( void ); + virtual bool IsValid( void ); + virtual ConCommandBase *Get( void ); + protected: + CCvar * const m_pOuter; + //CConCommandHash * const m_pHash; + //CConCommandHash::CCommandHashIterator_t m_hashIter; + ConCommandBase *m_pCur; + }; + + virtual ICVarIteratorInternal *FactoryInternalIterator( void ); + friend class CCVarIteratorInternal; + + enum ConVarSetType_t + { + CONVAR_SET_STRING = 0, + CONVAR_SET_INT, + CONVAR_SET_FLOAT, + }; + struct QueuedConVarSet_t + { + ConVar *m_pConVar; + ConVarSetType_t m_nType; + int m_nInt; + float m_flFloat; + CUtlString m_String; + }; + CUtlVector< QueuedConVarSet_t > m_QueuedConVarSets; + bool m_bMaterialSystemThreadSetAllowed; + +private: + // Standard console commands -- DO NOT PLACE ANY HIGHER THAN HERE BECAUSE THESE MUST BE THE FIRST TO DESTRUCT + CON_COMMAND_MEMBER_F( CCvar, "find", Find, "Find concommands with the specified string in their name/help text.", 0 ) +}; + +void CCvar::CCVarIteratorInternal::SetFirst( void ) RESTRICT +{ + //m_hashIter = m_pHash->First(); + m_pCur = m_pOuter->GetCommands(); +} + +void CCvar::CCVarIteratorInternal::Next( void ) RESTRICT +{ + //m_hashIter = m_pHash->Next( m_hashIter ); + if ( m_pCur ) + m_pCur = m_pCur->GetNext(); +} + +bool CCvar::CCVarIteratorInternal::IsValid( void ) RESTRICT +{ + //return m_pHash->IsValidIterator( m_hashIter ); + return m_pCur != NULL; +} + +ConCommandBase *CCvar::CCVarIteratorInternal::Get( void ) RESTRICT +{ + Assert( IsValid( ) ); + //return (*m_pHash)[m_hashIter]; + return m_pCur; +} + +ICvar::ICVarIteratorInternal *CCvar::FactoryInternalIterator( void ) +{ + return new CCVarIteratorInternal( this ); +} + +//----------------------------------------------------------------------------- +// Factor for CVars +//----------------------------------------------------------------------------- +static CCvar s_Cvar; +EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CCvar, ICvar, CVAR_INTERFACE_VERSION, s_Cvar ); + + +//----------------------------------------------------------------------------- +// Returns a CVar dictionary for tool usage +//----------------------------------------------------------------------------- +CreateInterfaceFn VStdLib_GetICVarFactory() +{ + return Sys_GetFactoryThis(); +} + + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CCvar::CCvar() : m_TempConsoleBuffer( 0, 1024 ) +{ + m_nNextDLLIdentifier = 0; + m_pConCommandList = NULL; + + m_bMaterialSystemThreadSetAllowed = false; +} + + +//----------------------------------------------------------------------------- +// Methods of IAppSystem +//----------------------------------------------------------------------------- +bool CCvar::Connect( CreateInterfaceFn factory ) +{ + ConnectTier1Libraries( &factory, 1 ); + + s_pCVarQuery = (ICvarQuery*)factory( CVAR_QUERY_INTERFACE_VERSION, NULL ); + if ( !s_pCVarQuery ) + { + s_pCVarQuery = &s_DefaultCvarQuery; + } + + ConVar_Register(); + return true; +} + +void CCvar::Disconnect() +{ + ConVar_Unregister(); + s_pCVarQuery = NULL; + DisconnectTier1Libraries(); +} + +InitReturnVal_t CCvar::Init() +{ + return INIT_OK; +} + +void CCvar::Shutdown() +{ +} + +void *CCvar::QueryInterface( const char *pInterfaceName ) +{ + // We implement the ICvar interface + if ( !V_strcmp( pInterfaceName, CVAR_INTERFACE_VERSION ) ) + return (ICvar*)this; + + return NULL; +} + + +//----------------------------------------------------------------------------- +// Method allowing the engine ICvarQuery interface to take over +//----------------------------------------------------------------------------- +void CCvar::InstallCVarQuery( ICvarQuery *pQuery ) +{ + Assert( s_pCVarQuery == &s_DefaultCvarQuery ); + s_pCVarQuery = pQuery ? pQuery : &s_DefaultCvarQuery; +} + + +//----------------------------------------------------------------------------- +// Used by DLLs to be able to unregister all their commands + convars +//----------------------------------------------------------------------------- +CVarDLLIdentifier_t CCvar::AllocateDLLIdentifier() +{ + return m_nNextDLLIdentifier++; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *variable - +//----------------------------------------------------------------------------- +void CCvar::RegisterConCommand( ConCommandBase *variable ) +{ + // Already registered + if ( variable->IsRegistered() ) + return; + + variable->m_bRegistered = true; + + const char *pName = variable->GetName(); + if ( !pName || !pName[0] ) + { + variable->m_pNext = NULL; + return; + } + + // If the variable is already defined, then setup the new variable as a proxy to it. + const ConCommandBase *pOther = FindVar( variable->GetName() ); + if ( pOther ) + { + if ( variable->IsCommand() || pOther->IsCommand() ) + { + Warning( "WARNING: unable to link %s and %s because one or more is a ConCommand.\n", variable->GetName(), pOther->GetName() ); + } + else + { + // This cast is ok because we make sure they're ConVars above. + const ConVar *pChildVar = static_cast< const ConVar* >( variable ); + const ConVar *pParentVar = static_cast< const ConVar* >( pOther ); + + // See if it's a valid linkage + if ( s_pCVarQuery->AreConVarsLinkable( pChildVar, pParentVar ) ) + { + // Make sure the default values are the same (but only spew about this for FCVAR_REPLICATED) + if( pChildVar->m_pszDefaultValue && pParentVar->m_pszDefaultValue && + pChildVar->IsFlagSet( FCVAR_REPLICATED ) && pParentVar->IsFlagSet( FCVAR_REPLICATED ) ) + { + if( Q_stricmp( pChildVar->m_pszDefaultValue, pParentVar->m_pszDefaultValue ) != 0 ) + { + Warning( "Parent and child ConVars with different default values! %s child: %s parent: %s (parent wins)\n", + variable->GetName(), pChildVar->m_pszDefaultValue, pParentVar->m_pszDefaultValue ); + } + } + + const_cast<ConVar*>( pChildVar )->m_pParent = const_cast<ConVar*>( pParentVar )->m_pParent; + + // Absorb material thread related convar flags + const_cast<ConVar*>( pParentVar )->m_nFlags |= pChildVar->m_nFlags & ( FCVAR_MATERIAL_THREAD_MASK | FCVAR_ACCESSIBLE_FROM_THREADS ); + + // check the parent's callbacks and slam if doesn't have, warn if both have callbacks + if( pChildVar->m_fnChangeCallback ) + { + if ( !pParentVar->m_fnChangeCallback ) + { + const_cast<ConVar*>( pParentVar )->m_fnChangeCallback = pChildVar->m_fnChangeCallback; + } + else + { + Warning( "Convar %s has multiple different change callbacks\n", variable->GetName() ); + } + } + + // make sure we don't have conflicting help strings. + if ( pChildVar->m_pszHelpString && Q_strlen( pChildVar->m_pszHelpString ) != 0 ) + { + if ( pParentVar->m_pszHelpString && Q_strlen( pParentVar->m_pszHelpString ) != 0 ) + { + if ( Q_stricmp( pParentVar->m_pszHelpString, pChildVar->m_pszHelpString ) != 0 ) + { + Warning( "Convar %s has multiple help strings:\n\tparent (wins): \"%s\"\n\tchild: \"%s\"\n", + variable->GetName(), pParentVar->m_pszHelpString, pChildVar->m_pszHelpString ); + } + } + else + { + const_cast<ConVar *>( pParentVar )->m_pszHelpString = pChildVar->m_pszHelpString; + } + } + + // make sure we don't have conflicting FCVAR_CHEAT flags. + if ( ( pChildVar->m_nFlags & FCVAR_CHEAT ) != ( pParentVar->m_nFlags & FCVAR_CHEAT ) ) + { + Warning( "Convar %s has conflicting FCVAR_CHEAT flags (child: %s, parent: %s, parent wins)\n", + variable->GetName(), ( pChildVar->m_nFlags & FCVAR_CHEAT ) ? "FCVAR_CHEAT" : "no FCVAR_CHEAT", + ( pParentVar->m_nFlags & FCVAR_CHEAT ) ? "FCVAR_CHEAT" : "no FCVAR_CHEAT" ); + } + + // make sure we don't have conflicting FCVAR_REPLICATED flags. + if ( ( pChildVar->m_nFlags & FCVAR_REPLICATED ) != ( pParentVar->m_nFlags & FCVAR_REPLICATED ) ) + { + Warning( "Convar %s has conflicting FCVAR_REPLICATED flags (child: %s, parent: %s, parent wins)\n", + variable->GetName(), ( pChildVar->m_nFlags & FCVAR_REPLICATED ) ? "FCVAR_REPLICATED" : "no FCVAR_REPLICATED", + ( pParentVar->m_nFlags & FCVAR_REPLICATED ) ? "FCVAR_REPLICATED" : "no FCVAR_REPLICATED" ); + } + + // make sure we don't have conflicting FCVAR_DONTRECORD flags. + if ( ( pChildVar->m_nFlags & FCVAR_DONTRECORD ) != ( pParentVar->m_nFlags & FCVAR_DONTRECORD ) ) + { + Warning( "Convar %s has conflicting FCVAR_DONTRECORD flags (child: %s, parent: %s, parent wins)\n", + variable->GetName(), ( pChildVar->m_nFlags & FCVAR_DONTRECORD ) ? "FCVAR_DONTRECORD" : "no FCVAR_DONTRECORD", + ( pParentVar->m_nFlags & FCVAR_DONTRECORD ) ? "FCVAR_DONTRECORD" : "no FCVAR_DONTRECORD" ); + } + } + } + + variable->m_pNext = NULL; + return; + } + + // link the variable in + variable->m_pNext = m_pConCommandList; + m_pConCommandList = variable; +} + +void CCvar::UnregisterConCommand( ConCommandBase *pCommandToRemove ) +{ + // Not registered? Don't bother + if ( !pCommandToRemove->IsRegistered() ) + return; + + pCommandToRemove->m_bRegistered = false; + + // FIXME: Should we make this a doubly-linked list? Would remove faster + ConCommandBase *pPrev = NULL; + for( ConCommandBase *pCommand = m_pConCommandList; pCommand; pCommand = pCommand->m_pNext ) + { + if ( pCommand != pCommandToRemove ) + { + pPrev = pCommand; + continue; + } + + if ( pPrev == NULL ) + { + m_pConCommandList = pCommand->m_pNext; + } + else + { + pPrev->m_pNext = pCommand->m_pNext; + } + pCommand->m_pNext = NULL; + break; + } +} + +// Crash here in TF2, so I'm adding some debugging stuff. +#ifdef WIN32 +#pragma optimize( "", off ) +#endif +void CCvar::UnregisterConCommands( CVarDLLIdentifier_t id ) +{ + ConCommandBase *pNewList; + ConCommandBase *pCommand, *pNext; + + int iCommandsLooped = 0; + + pNewList = NULL; + pCommand = m_pConCommandList; + while ( pCommand ) + { + pNext = pCommand->m_pNext; + if ( pCommand->GetDLLIdentifier() != id ) + { + pCommand->m_pNext = pNewList; + pNewList = pCommand; + } + else + { + // Unlink + pCommand->m_bRegistered = false; + pCommand->m_pNext = NULL; + } + + pCommand = pNext; + iCommandsLooped++; + } + + m_pConCommandList = pNewList; +} +#ifdef WIN32 +#pragma optimize( "", on ) +#endif + + +//----------------------------------------------------------------------------- +// Finds base commands +//----------------------------------------------------------------------------- +const ConCommandBase *CCvar::FindCommandBase( const char *name ) const +{ + const ConCommandBase *cmd = GetCommands(); + for ( ; cmd; cmd = cmd->GetNext() ) + { + if ( !Q_stricmp( name, cmd->GetName() ) ) + return cmd; + } + return NULL; +} + +ConCommandBase *CCvar::FindCommandBase( const char *name ) +{ + ConCommandBase *cmd = GetCommands(); + for ( ; cmd; cmd = cmd->GetNext() ) + { + if ( !Q_stricmp( name, cmd->GetName() ) ) + return cmd; + } + return NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose Finds ConVars +//----------------------------------------------------------------------------- +const ConVar *CCvar::FindVar( const char *var_name ) const +{ + VPROF_INCREMENT_COUNTER( "CCvar::FindVar", 1 ); + VPROF( "CCvar::FindVar" ); + const ConCommandBase *var = FindCommandBase( var_name ); + if ( !var || var->IsCommand() ) + return NULL; + + return static_cast<const ConVar*>(var); +} + +ConVar *CCvar::FindVar( const char *var_name ) +{ + VPROF_INCREMENT_COUNTER( "CCvar::FindVar", 1 ); + VPROF( "CCvar::FindVar" ); + ConCommandBase *var = FindCommandBase( var_name ); + if ( !var || var->IsCommand() ) + return NULL; + + return static_cast<ConVar*>( var ); +} + + +//----------------------------------------------------------------------------- +// Purpose Finds ConCommands +//----------------------------------------------------------------------------- +const ConCommand *CCvar::FindCommand( const char *pCommandName ) const +{ + const ConCommandBase *var = FindCommandBase( pCommandName ); + if ( !var || !var->IsCommand() ) + return NULL; + + return static_cast<const ConCommand*>(var); +} + +ConCommand *CCvar::FindCommand( const char *pCommandName ) +{ + ConCommandBase *var = FindCommandBase( pCommandName ); + if ( !var || !var->IsCommand() ) + return NULL; + + return static_cast<ConCommand*>( var ); +} + + +const char* CCvar::GetCommandLineValue( const char *pVariableName ) +{ + int nLen = Q_strlen(pVariableName); + char *pSearch = (char*)stackalloc( nLen + 2 ); + pSearch[0] = '+'; + memcpy( &pSearch[1], pVariableName, nLen + 1 ); + return CommandLine()->ParmValue( pSearch ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +ConCommandBase *CCvar::GetCommands( void ) +{ + return m_pConCommandList; +} + +const ConCommandBase *CCvar::GetCommands( void ) const +{ + return m_pConCommandList; +} + + +//----------------------------------------------------------------------------- +// Install, remove global callbacks +//----------------------------------------------------------------------------- +void CCvar::InstallGlobalChangeCallback( FnChangeCallback_t callback ) +{ + Assert( callback && m_GlobalChangeCallbacks.Find( callback ) < 0 ); + m_GlobalChangeCallbacks.AddToTail( callback ); +} + +void CCvar::RemoveGlobalChangeCallback( FnChangeCallback_t callback ) +{ + Assert( callback ); + m_GlobalChangeCallbacks.FindAndRemove( callback ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCvar::CallGlobalChangeCallbacks( ConVar *var, const char *pOldString, float flOldValue ) +{ + int nCallbackCount = m_GlobalChangeCallbacks.Count(); + for ( int i = 0; i < nCallbackCount; ++i ) + { + (*m_GlobalChangeCallbacks[i])( var, pOldString, flOldValue ); + } +} + + +//----------------------------------------------------------------------------- +// Sets convars containing the flags to their default value +//----------------------------------------------------------------------------- +void CCvar::RevertFlaggedConVars( int nFlag ) +{ + for (const ConCommandBase *var= GetCommands() ; var ; var=var->GetNext()) + { + if ( var->IsCommand() ) + continue; + + ConVar *pCvar = ( ConVar * )var; + + if ( !pCvar->IsFlagSet( nFlag ) ) + continue; + + // It's == to the default value, don't count + if ( !Q_stricmp( pCvar->GetDefault(), pCvar->GetString() ) ) + continue; + + pCvar->Revert(); + + // DevMsg( "%s = \"%s\" (reverted)\n", cvar->GetName(), cvar->GetString() ); + } +} + + +//----------------------------------------------------------------------------- +// Deal with queued material system convars +//----------------------------------------------------------------------------- +bool CCvar::IsMaterialThreadSetAllowed( ) const +{ + Assert( ThreadInMainThread() ); + return m_bMaterialSystemThreadSetAllowed; +} + +void CCvar::QueueMaterialThreadSetValue( ConVar *pConVar, const char *pValue ) +{ + Assert( ThreadInMainThread() ); + int j = m_QueuedConVarSets.AddToTail(); + m_QueuedConVarSets[j].m_pConVar = pConVar; + m_QueuedConVarSets[j].m_nType = CONVAR_SET_STRING; + m_QueuedConVarSets[j].m_String = pValue; +} + +void CCvar::QueueMaterialThreadSetValue( ConVar *pConVar, int nValue ) +{ + Assert( ThreadInMainThread() ); + int j = m_QueuedConVarSets.AddToTail(); + m_QueuedConVarSets[j].m_pConVar = pConVar; + m_QueuedConVarSets[j].m_nType = CONVAR_SET_INT; + m_QueuedConVarSets[j].m_nInt = nValue; +} + +void CCvar::QueueMaterialThreadSetValue( ConVar *pConVar, float flValue ) +{ + Assert( ThreadInMainThread() ); + int j = m_QueuedConVarSets.AddToTail(); + m_QueuedConVarSets[j].m_pConVar = pConVar; + m_QueuedConVarSets[j].m_nType = CONVAR_SET_FLOAT; + m_QueuedConVarSets[j].m_flFloat = flValue; +} + +bool CCvar::HasQueuedMaterialThreadConVarSets() const +{ + Assert( ThreadInMainThread() ); + return m_QueuedConVarSets.Count() > 0; +} + +int CCvar::ProcessQueuedMaterialThreadConVarSets() +{ + Assert( ThreadInMainThread() ); + m_bMaterialSystemThreadSetAllowed = true; + + int nUpdateFlags = 0; + int nCount = m_QueuedConVarSets.Count(); + for ( int i = 0; i < nCount; ++i ) + { + const QueuedConVarSet_t& set = m_QueuedConVarSets[i]; + switch( set.m_nType ) + { + case CONVAR_SET_FLOAT: + set.m_pConVar->SetValue( set.m_flFloat ); + break; + case CONVAR_SET_INT: + set.m_pConVar->SetValue( set.m_nInt ); + break; + case CONVAR_SET_STRING: + set.m_pConVar->SetValue( set.m_String ); + break; + } + + nUpdateFlags |= set.m_pConVar->GetFlags() & FCVAR_MATERIAL_THREAD_MASK; + } + + m_QueuedConVarSets.RemoveAll(); + m_bMaterialSystemThreadSetAllowed = false; + return nUpdateFlags; +} + + +//----------------------------------------------------------------------------- +// Display queued messages +//----------------------------------------------------------------------------- +void CCvar::DisplayQueuedMessages( ) +{ + // Display any queued up messages + if ( m_TempConsoleBuffer.TellPut() == 0 ) + return; + + Color clr; + int nStringLength; + while( m_TempConsoleBuffer.IsValid() ) + { + int nType = m_TempConsoleBuffer.GetChar(); + if ( nType == CONSOLE_COLOR_PRINT ) + { + clr.SetRawColor( m_TempConsoleBuffer.GetInt() ); + } + nStringLength = m_TempConsoleBuffer.PeekStringLength(); + char* pTemp = (char*)stackalloc( nStringLength + 1 ); + m_TempConsoleBuffer.GetStringManualCharCount( pTemp, nStringLength + 1 ); + + switch( nType ) + { + case CONSOLE_COLOR_PRINT: + ConsoleColorPrintf( clr, pTemp ); + break; + + case CONSOLE_PRINT: + ConsolePrintf( pTemp ); + break; + + case CONSOLE_DPRINT: + ConsoleDPrintf( pTemp ); + break; + } + } + + m_TempConsoleBuffer.Purge(); +} + + +//----------------------------------------------------------------------------- +// Install a console printer +//----------------------------------------------------------------------------- +void CCvar::InstallConsoleDisplayFunc( IConsoleDisplayFunc* pDisplayFunc ) +{ + Assert( m_DisplayFuncs.Find( pDisplayFunc ) < 0 ); + m_DisplayFuncs.AddToTail( pDisplayFunc ); + DisplayQueuedMessages(); +} + +void CCvar::RemoveConsoleDisplayFunc( IConsoleDisplayFunc* pDisplayFunc ) +{ + m_DisplayFuncs.FindAndRemove( pDisplayFunc ); +} + +void CCvar::ConsoleColorPrintf( const Color& clr, const char *pFormat, ... ) const +{ + char temp[ 8192 ]; + va_list argptr; + va_start( argptr, pFormat ); + _vsnprintf( temp, sizeof( temp ) - 1, pFormat, argptr ); + va_end( argptr ); + temp[ sizeof( temp ) - 1 ] = 0; + + int c = m_DisplayFuncs.Count(); + if ( c == 0 ) + { + m_TempConsoleBuffer.PutChar( CONSOLE_COLOR_PRINT ); + m_TempConsoleBuffer.PutInt( clr.GetRawColor() ); + m_TempConsoleBuffer.PutString( temp ); + return; + } + + for ( int i = 0 ; i < c; ++i ) + { + m_DisplayFuncs[ i ]->ColorPrint( clr, temp ); + } +} + +void CCvar::ConsolePrintf( const char *pFormat, ... ) const +{ + char temp[ 8192 ]; + va_list argptr; + va_start( argptr, pFormat ); + _vsnprintf( temp, sizeof( temp ) - 1, pFormat, argptr ); + va_end( argptr ); + temp[ sizeof( temp ) - 1 ] = 0; + + int c = m_DisplayFuncs.Count(); + if ( c == 0 ) + { + m_TempConsoleBuffer.PutChar( CONSOLE_PRINT ); + m_TempConsoleBuffer.PutString( temp ); + return; + } + + for ( int i = 0 ; i < c; ++i ) + { + m_DisplayFuncs[ i ]->Print( temp ); + } +} + +void CCvar::ConsoleDPrintf( const char *pFormat, ... ) const +{ + char temp[ 8192 ]; + va_list argptr; + va_start( argptr, pFormat ); + _vsnprintf( temp, sizeof( temp ) - 1, pFormat, argptr ); + va_end( argptr ); + temp[ sizeof( temp ) - 1 ] = 0; + + int c = m_DisplayFuncs.Count(); + if ( c == 0 ) + { + m_TempConsoleBuffer.PutChar( CONSOLE_DPRINT ); + m_TempConsoleBuffer.PutString( temp ); + return; + } + + for ( int i = 0 ; i < c; ++i ) + { + m_DisplayFuncs[ i ]->DPrint( temp ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +#if defined( _X360 ) + +void CCvar::PublishToVXConsole() +{ + const char *commands[4096]; + const char *helptext[4096]; + const ConCommandBase *pCur; + int numCommands = 0; + + // iterate and publish commands to the remote console + for ( pCur = m_pConCommandList; pCur; pCur=pCur->GetNext() ) + { + // add unregistered commands to list + if ( numCommands < sizeof(commands)/sizeof(commands[0]) ) + { + commands[numCommands] = pCur->GetName(); + helptext[numCommands] = pCur->GetHelpText(); + numCommands++; + } + } + + if ( numCommands ) + { + XBX_rAddCommands( numCommands, commands, helptext ); + } +} + +#endif + + +//----------------------------------------------------------------------------- +// Console commands +//----------------------------------------------------------------------------- +void CCvar::Find( const CCommand &args ) +{ + const char *search; + const ConCommandBase *var; + + if ( args.ArgC() != 2 ) + { + ConMsg( "Usage: find <string>\n" ); + return; + } + + // Get substring to find + search = args[1]; + + // Loop through vars and print out findings + for ( var = GetCommands(); var; var=var->GetNext() ) + { + if ( var->IsFlagSet(FCVAR_DEVELOPMENTONLY) || var->IsFlagSet(FCVAR_HIDDEN) ) + continue; + + if ( !Q_stristr( var->GetName(), search ) && + !Q_stristr( var->GetHelpText(), search ) ) + continue; + + ConVar_PrintDescription( var ); + } +} + + diff --git a/vstdlib/getstackptr64.masm b/vstdlib/getstackptr64.masm new file mode 100644 index 0000000..52354b6 --- /dev/null +++ b/vstdlib/getstackptr64.masm @@ -0,0 +1,17 @@ +; call cpuid with args in eax, ecx +; store eax, ebx, ecx, edx to p +PUBLIC GetStackPtr64 +.CODE + +GetStackPtr64 PROC FRAME +; unsigned char* GetStackPtr64(void); + .endprolog + + mov rax, rsp ; get stack ptr + add rax, 8h ; account for 8-byte return value of this function + + ret + +GetStackPtr64 ENDP +_TEXT ENDS +END diff --git a/vstdlib/jobthread.cpp b/vstdlib/jobthread.cpp new file mode 100644 index 0000000..46d843e --- /dev/null +++ b/vstdlib/jobthread.cpp @@ -0,0 +1,1457 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#if defined( _WIN32 ) && !defined( _X360 ) +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#endif +#include "tier0/dbg.h" +#include "tier0/tslist.h" +#include "tier0/icommandline.h" +#include "vstdlib/jobthread.h" +#include "vstdlib/random.h" +#include "tier1/functors.h" +#include "tier1/fmtstr.h" +#include "tier1/utlvector.h" +#include "tier1/generichash.h" +#include "tier0/vprof.h" + +#if defined( _X360 ) +#include "xbox/xbox_win32stubs.h" +#endif + +#include "tier0/memdbgon.h" + + +class CJobThread; + +//----------------------------------------------------------------------------- + +inline void ServiceJobAndRelease( CJob *pJob, int iThread = -1 ) +{ + // TryLock() would only fail if another thread has entered + // Execute() or Abort() + if ( !pJob->IsFinished() && pJob->TryLock() ) + { + // ...service the request + pJob->SetServiceThread( iThread ); + pJob->Execute(); + pJob->Unlock(); + } + pJob->Release(); +} + +//----------------------------------------------------------------------------- + +class ALIGN16 CJobQueue +{ +public: + CJobQueue() : + m_nItems( 0 ), + m_nMaxItems( INT_MAX ) + { + for ( int i = 0; i < ARRAYSIZE( m_pQueues ); i++ ) + { + m_pQueues[i] = new CTSQueue<CJob *>; + } + } + + ~CJobQueue() + { + for ( int i = 0; i < ARRAYSIZE( m_pQueues ); i++ ) + { + delete m_pQueues[i]; + } + } + + int Count() + { + return m_nItems; + } + + int Count( JobPriority_t priority ) + { + return m_pQueues[priority]->Count(); + } + + + CJob *PrePush() + { + if ( m_nItems >= m_nMaxItems ) + { + CJob *pOverflowJob; + if ( Pop( &pOverflowJob ) ) + { + return pOverflowJob; + } + } + return NULL; + } + + int Push( CJob *pJob, int iThread = -1 ) + { + pJob->AddRef(); + + CJob *pOverflowJob; + int nOverflow = 0; + while ( ( pOverflowJob = PrePush() ) != NULL ) + { + ServiceJobAndRelease( pJob ); + nOverflow++; + } + + m_pQueues[pJob->GetPriority()]->PushItem( pJob ); + + m_mutex.Lock(); + if ( ++m_nItems == 1 ) + { + m_JobAvailableEvent.Set(); + } + m_mutex.Unlock(); + + return nOverflow; + } + + bool Pop( CJob **ppJob ) + { + m_mutex.Lock(); + if ( !m_nItems ) + { + m_mutex.Unlock(); + *ppJob = NULL; + return false; + } + if ( --m_nItems == 0 ) + { + m_JobAvailableEvent.Reset(); + } + m_mutex.Unlock(); + + for ( int i = JP_HIGH; i >= 0; --i ) + { + if ( m_pQueues[i]->PopItem( ppJob ) ) + { + return true; + } + } + + + AssertMsg( 0, "Expected at least one queue item" ); + *ppJob = NULL; + return false; + } + + CThreadEvent &GetEventHandle() + { + return m_JobAvailableEvent; + } + + void Flush() + { + // Only safe to call when system is suspended + m_mutex.Lock(); + m_nItems = 0; + m_JobAvailableEvent.Reset(); + CJob *pJob; + for ( int i = JP_HIGH; i >= 0; --i ) + { + while ( m_pQueues[i]->PopItem( &pJob ) ) + { + pJob->Abort(); + pJob->Release(); + } + } + m_mutex.Unlock(); + } + +private: + CTSQueue<CJob *> *m_pQueues[JP_HIGH + 1]; + int m_nItems; + int m_nMaxItems; + CThreadMutex m_mutex; + CThreadManualEvent m_JobAvailableEvent; + +} ALIGN16_POST; + +//----------------------------------------------------------------------------- +// +// CThreadPool +// +//----------------------------------------------------------------------------- + +class CThreadPool : public CRefCounted1<IThreadPool, CRefCountServiceMT> +{ +public: + CThreadPool(); + ~CThreadPool(); + + //----------------------------------------------------- + // Thread functions + //----------------------------------------------------- + bool Start( const ThreadPoolStartParams_t &startParams = ThreadPoolStartParams_t() ) { return Start( startParams, NULL ); } + bool Start( const ThreadPoolStartParams_t &startParams, const char *pszNameOverride ); + bool Stop( int timeout = TT_INFINITE ); + void Distribute( bool bDistribute = true, int *pAffinityTable = NULL ); + + //----------------------------------------------------- + // Functions for any thread + //----------------------------------------------------- + unsigned GetJobCount() { return m_nJobs; } + int NumThreads(); + int NumIdleThreads(); + + //----------------------------------------------------- + // Pause/resume processing jobs + //----------------------------------------------------- + int SuspendExecution(); + int ResumeExecution(); + + //----------------------------------------------------- + // Offer the current thread to the pool + //----------------------------------------------------- + virtual int YieldWait( CThreadEvent **pEvents, int nEvents, bool bWaitAll = true, unsigned timeout = TT_INFINITE ); + virtual int YieldWait( CJob **, int nJobs, bool bWaitAll = true, unsigned timeout = TT_INFINITE ); + void Yield( unsigned timeout ); + + //----------------------------------------------------- + // Add a native job to the queue (master thread) + //----------------------------------------------------- + void AddJob( CJob * ); + void InsertJobInQueue( CJob * ); + + //----------------------------------------------------- + // All threads execute pFunctor asap. Thread will either wake up + // and execute or execute pFunctor right after completing current job and + // before looking for another job. + //----------------------------------------------------- + void ExecuteHighPriorityFunctor( CFunctor *pFunctor ); + + //----------------------------------------------------- + // Add an function object to the queue (master thread) + //----------------------------------------------------- + void AddFunctorInternal( CFunctor *, CJob ** = NULL, const char *pszDescription = NULL, unsigned flags = 0 ); + + //----------------------------------------------------- + // Remove a job from the queue (master thread) + //----------------------------------------------------- + virtual void ChangePriority( CJob *p, JobPriority_t priority ); + + //----------------------------------------------------- + // Bulk job manipulation (blocking) + //----------------------------------------------------- + int ExecuteToPriority( JobPriority_t toPriority, JobFilter_t pfnFilter = NULL ); + int AbortAll(); + + virtual void Reserved1() {} + + void WaitForIdle( bool bAll = true ); + +private: + enum + { + IO_STACKSIZE = ( 64 * 1024 ), + COMPUTATION_STACKSIZE = 0, + }; + + //----------------------------------------------------- + // + //----------------------------------------------------- + CJob *PeekJob(); + CJob *GetDummyJob(); + + //----------------------------------------------------- + // Thread functions + //----------------------------------------------------- + int Run(); + +private: + friend class CJobThread; + + CJobQueue m_SharedQueue; + CInterlockedInt m_nIdleThreads; + CUtlVector<CJobThread *> m_Threads; + CUtlVector<CThreadEvent *> m_IdleEvents; + + CThreadMutex m_SuspendMutex; + int m_nSuspend; + CInterlockedInt m_nJobs; + + // Some jobs should only be executed on the threadpool thread(s). Ie: the rendering thread has the GL context + // and the main thread coming in and "helping" with jobs breaks that pretty nicely. This flag states that + // only the threadpool threads should execute these jobs. + bool m_bExecOnThreadPoolThreadsOnly; +}; + +//----------------------------------------------------------------------------- + +JOB_INTERFACE IThreadPool *CreateThreadPool() +{ + return new CThreadPool; +} + +JOB_INTERFACE void DestroyThreadPool( IThreadPool *pPool ) +{ + delete pPool; +} + +//----------------------------------------------------------------------------- + +class CGlobalThreadPool : public CThreadPool +{ +public: + virtual bool Start( const ThreadPoolStartParams_t &startParamsIn ) + { + int nThreads = ( CommandLine()->ParmValue( "-threads", -1 ) - 1 ); + ThreadPoolStartParams_t startParams = startParamsIn; + + if ( nThreads >= 0 ) + { + startParams.nThreads = nThreads; + } + else + { + // Cap the GlobPool threads at 4. + startParams.nThreadsMax = 4; + } + return CThreadPool::Start( startParams, "Glob" ); + } + + virtual bool OnFinalRelease() + { + AssertMsg( 0, "Releasing global thread pool object!" ); + return false; + } +}; + +//----------------------------------------------------------------------------- + +class CJobThread : public CWorkerThread +{ +public: + CJobThread( CThreadPool *pOwner, int iThread ) : + m_SharedQueue( pOwner->m_SharedQueue ), + m_pOwner( pOwner ), + m_iThread( iThread ) + { + } + + CThreadEvent &GetIdleEvent() + { + return m_IdleEvent; + } + + CJobQueue &AccessDirectQueue() + { + return m_DirectQueue; + } + +private: + unsigned Wait() + { + unsigned waitResult; + tmZone( TELEMETRY_LEVEL0, TMZF_IDLE, "%s", __FUNCTION__ ); +#ifdef WIN32 + enum Event_t + { + CALL_FROM_MASTER, + SHARED_QUEUE, + DIRECT_QUEUE, + + NUM_EVENTS + }; + + HANDLE waitHandles[NUM_EVENTS]; + + waitHandles[CALL_FROM_MASTER] = GetCallHandle().GetHandle(); + waitHandles[SHARED_QUEUE] = m_SharedQueue.GetEventHandle().GetHandle(); + waitHandles[DIRECT_QUEUE] = m_DirectQueue.GetEventHandle().GetHandle(); + +#ifdef _DEBUG + while ( ( waitResult = WaitForMultipleObjects( ARRAYSIZE(waitHandles), waitHandles, FALSE, 10 ) ) == WAIT_TIMEOUT ) + { + waitResult = waitResult; // break here + } +#else + waitResult = WaitForMultipleObjects( ARRAYSIZE(waitHandles), waitHandles, FALSE, INFINITE ); +#endif +#else // !win32 + bool bSet = false; + int nWaitTime = 100; + + while( !bSet ) + { + // Jobs are typically enqueued to the shared job queue so wait on it first. + bSet = m_SharedQueue.GetEventHandle().Wait( nWaitTime ); + if( !bSet ) + bSet = m_DirectQueue.GetEventHandle().Wait( 10 ); + if ( !bSet ) + bSet = GetCallHandle().Wait( 0 ); + } + + if ( !bSet ) + waitResult = WAIT_TIMEOUT; + else + waitResult = WAIT_OBJECT_0; +#endif + return waitResult; + } + + int Run() + { + + + // Wait for either a call from the master thread, or an item in the queue... + unsigned waitResult; + bool bExit = false; + + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); + + m_pOwner->m_nIdleThreads++; + m_IdleEvent.Set(); + while (!bExit && ( ( waitResult = Wait() ) != WAIT_FAILED ) ) + { + if ( PeekCall() ) + { + CFunctor *pFunctor = NULL; + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s PeekCall():%d", __FUNCTION__, GetCallParam() ); + + switch ( GetCallParam( &pFunctor ) ) + { + case TPM_EXIT: + Reply( true ); + bExit = TRUE; + break; + + case TPM_SUSPEND: + Reply( true ); + SuspendCooperative(); + break; + + case TPM_RUNFUNCTOR: + if( pFunctor ) + { + ( *pFunctor )(); + Reply( true ); + } + else + { + Assert( pFunctor ); + Reply( false ); + } + break; + + default: + AssertMsg( 0, "Unknown call to thread" ); + Reply( false ); + break; + } + } + else + { + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s !PeekCall()", __FUNCTION__ ); + + CJob *pJob; + bool bTookJob = false; + do + { + if ( !m_DirectQueue.Pop( &pJob) ) + { + if ( !m_SharedQueue.Pop( &pJob ) ) + { + // Nothing to process, return to wait state + break; + } + } + if ( !bTookJob ) + { + m_IdleEvent.Reset(); + m_pOwner->m_nIdleThreads--; + bTookJob = true; + } + ServiceJobAndRelease( pJob, m_iThread ); + m_pOwner->m_nJobs--; + } while ( !PeekCall() ); + + if ( bTookJob ) + { + m_pOwner->m_nIdleThreads++; + m_IdleEvent.Set(); + } + } + } + m_pOwner->m_nIdleThreads--; + m_IdleEvent.Reset(); + return 0; + } + + CJobQueue m_DirectQueue; + CJobQueue & m_SharedQueue; + CThreadPool * m_pOwner; + CThreadManualEvent m_IdleEvent; + int m_iThread; +}; + +//----------------------------------------------------------------------------- + +CGlobalThreadPool g_ThreadPool; +IThreadPool *g_pThreadPool = &g_ThreadPool; + +//----------------------------------------------------------------------------- +// +// CThreadPool +// +//----------------------------------------------------------------------------- + +CThreadPool::CThreadPool() : + m_nIdleThreads( 0 ), + m_nJobs( 0 ), + m_nSuspend( 0 ) +{ +} + +//--------------------------------------------------------- + +CThreadPool::~CThreadPool() +{ + Stop(); +} + +//--------------------------------------------------------- +// +//--------------------------------------------------------- +int CThreadPool::NumThreads() +{ + return m_Threads.Count(); +} + +//--------------------------------------------------------- +// +//--------------------------------------------------------- +int CThreadPool::NumIdleThreads() +{ + return m_nIdleThreads; +} + +void CThreadPool::ExecuteHighPriorityFunctor( CFunctor *pFunctor ) +{ + int i; + for ( i = 0; i < m_Threads.Count(); i++ ) + { + m_Threads[i]->CallWorker( TPM_RUNFUNCTOR, 0, false, pFunctor ); + } + + for ( i = 0; i < m_Threads.Count(); i++ ) + { + m_Threads[i]->WaitForReply(); + } +} + +//--------------------------------------------------------- +// Pause/resume processing jobs +//--------------------------------------------------------- +int CThreadPool::SuspendExecution() +{ + AUTO_LOCK( m_SuspendMutex ); + + // If not already suspended + if ( m_nSuspend == 0 ) + { + // Make sure state is correct + int i; + for ( i = 0; i < m_Threads.Count(); i++ ) + { + m_Threads[i]->CallWorker( TPM_SUSPEND, 0 ); + } + + for ( i = 0; i < m_Threads.Count(); i++ ) + { + m_Threads[i]->WaitForReply(); + } + + // Because worker must signal before suspending, we could reach + // here with the thread not actually suspended + for ( i = 0; i < m_Threads.Count(); i++ ) + { + m_Threads[i]->BWaitForThreadSuspendCooperative(); + } + } + + return m_nSuspend++; +} + +//--------------------------------------------------------- + +int CThreadPool::ResumeExecution() +{ + AUTO_LOCK( m_SuspendMutex ); + AssertMsg( m_nSuspend >= 1, "Attempted resume when not suspended"); + int result = m_nSuspend--; + if (m_nSuspend == 0 ) + { + for ( int i = 0; i < m_Threads.Count(); i++ ) + { + m_Threads[i]->ResumeCooperative(); + } + } + return result; +} + +//--------------------------------------------------------- + +void CThreadPool::WaitForIdle( bool bAll ) +{ + ThreadWaitForEvents( m_IdleEvents.Count(), m_IdleEvents.Base(), bAll, 60000 ); +} + +//--------------------------------------------------------- + +int CThreadPool::YieldWait( CThreadEvent **pEvents, int nEvents, bool bWaitAll, unsigned timeout ) +{ + tmZone( TELEMETRY_LEVEL0, TMZF_IDLE, "%s(%d) SPINNING %t", __FUNCTION__, timeout, tmSendCallStack( TELEMETRY_LEVEL0, 0 ) ); + + Assert( timeout == TT_INFINITE ); // unimplemented + + int result; + CJob *pJob; + // Always wait for zero milliseconds initially, to let us process jobs on this thread. + timeout = 0; + while ( ( result = ThreadWaitForEvents( nEvents, pEvents, bWaitAll, timeout ) ) == WAIT_TIMEOUT ) + { + if ( !m_bExecOnThreadPoolThreadsOnly && m_SharedQueue.Pop( &pJob ) ) + { + ServiceJobAndRelease( pJob ); + m_nJobs--; + } + else + { + // Since there are no jobs for the main thread set the timeout to infinite. + // The only disadvantage to this is that if a job thread creates a new job + // then the main thread will not be available to pick it up, but if that + // is a problem you can just create more worker threads. Debugging test runs + // of TF2 suggests that jobs are only ever added from the main thread which + // means that there is no disadvantage. + // Waiting on the events instead of busy spinning has multiple advantages. + // It avoids wasting CPU time/electricity, it makes it more obvious in profiles + // when the main thread is idle versus busy, and it allows ready thread analysis + // in xperf to find out what woke up a waiting thread. + // It also avoids unnecessary CPU starvation -- seen on customer traces of TF2. + timeout = TT_INFINITE; + } + } + return result; +} + +//--------------------------------------------------------- + +int CThreadPool::YieldWait( CJob **ppJobs, int nJobs, bool bWaitAll, unsigned timeout ) +{ + CUtlVectorFixed<CThreadEvent *, 64> handles; + if ( nJobs > handles.NumAllocated() - 2 ) + { + return TW_FAILED; + } + + for ( int i = 0; i < nJobs; i++ ) + { + handles.AddToTail( ppJobs[i]->AccessEvent() ); + } + + return YieldWait( handles.Base(), handles.Count(), bWaitAll, timeout); +} + +//--------------------------------------------------------- + +void CThreadPool::Yield( unsigned timeout ) +{ + // @MULTICORE (toml 10/24/2006): not implemented + Assert( ThreadInMainThread() ); + if ( !ThreadInMainThread() ) + { + ThreadSleep( timeout ); + return; + } + ThreadSleep( timeout ); +} + +//--------------------------------------------------------- +// Add a job to the queue +//--------------------------------------------------------- + +void CThreadPool::AddJob( CJob *pJob ) +{ + if ( !pJob ) + { + return; + } + + if ( pJob->m_ThreadPoolData != JOB_NO_DATA ) + { + Warning( "Cannot add a thread job already committed to another thread pool\n" ); + return; + } + + if ( m_Threads.Count() == 0 ) + { + // So only threadpool jobs are supposed to execute the jobs, but there are no threadpool threads? + Assert( !m_bExecOnThreadPoolThreadsOnly ); + + pJob->Execute(); + return; + } + + int flags = pJob->GetFlags(); + + if ( !m_bExecOnThreadPoolThreadsOnly && ( ( flags & ( JF_IO | JF_QUEUE ) ) == 0 ) /* @TBD && !m_queue.Count() */ ) + { + if ( !NumIdleThreads() ) + { + pJob->Execute(); + return; + } + pJob->SetPriority( JP_HIGH ); + } + + + if ( !pJob->CanExecute() ) + { + // Already handled + ExecuteOnce( Warning( "Attempted to add job to job queue that has already been completed\n" ) ); + return; + } + + pJob->m_pThreadPool = this; + pJob->m_status = JOB_STATUS_PENDING; + InsertJobInQueue( pJob ); + ++m_nJobs; +} + +//--------------------------------------------------------- +// +//--------------------------------------------------------- + +void CThreadPool::InsertJobInQueue( CJob *pJob ) +{ + CJobQueue *pQueue; + + if ( !( pJob->GetFlags() & JF_SERIAL ) ) + { + int iThread = pJob->GetServiceThread(); + if ( iThread == -1 || !m_Threads.IsValidIndex( iThread ) ) + { + pQueue = &m_SharedQueue; + } + else + { + pQueue = &(m_Threads[iThread]->AccessDirectQueue()); + } + } + else + { + pQueue = &(m_Threads[0]->AccessDirectQueue()); + } + + m_nJobs -= pQueue->Push( pJob ); +} + +//--------------------------------------------------------- +// Add an function object to the queue (master thread) +//--------------------------------------------------------- + +void CThreadPool::AddFunctorInternal( CFunctor *pFunctor, CJob **ppJob, const char *pszDescription, unsigned flags ) +{ + // Note: assumes caller has handled refcount + CJob *pJob = new CFunctorJob( pFunctor, pszDescription ); + + pJob->SetFlags( flags ); + + AddJob( pJob ); + + if ( ppJob ) + { + *ppJob = pJob; + } + else + { + pJob->Release(); + } +} + +//--------------------------------------------------------- +// Remove a job from the queue +//--------------------------------------------------------- + +void CThreadPool::ChangePriority( CJob *pJob, JobPriority_t priority ) +{ + // Right now, only support upping the priority + if ( pJob->GetPriority() < priority ) + { + pJob->SetPriority( priority ); + m_SharedQueue.Push( pJob ); + } + else + { + ExecuteOnce( if ( pJob->GetPriority() != priority ) DevMsg( "CThreadPool::RemoveJob not implemented right now" ) ); + } + +} + +//--------------------------------------------------------- +// Execute to a specified priority +//--------------------------------------------------------- + +int CThreadPool::ExecuteToPriority( JobPriority_t iToPriority, JobFilter_t pfnFilter ) +{ + SuspendExecution(); + + CJob *pJob; + int nExecuted = 0; + int i; + int nJobsTotal = GetJobCount(); + CUtlVector<CJob *> jobsToPutBack; + + for ( int iCurPriority = JP_HIGH; iCurPriority >= iToPriority; --iCurPriority ) + { + for ( i = 0; i < m_Threads.Count(); i++ ) + { + CJobQueue &queue = m_Threads[i]->AccessDirectQueue(); + while ( queue.Count( (JobPriority_t)iCurPriority ) ) + { + queue.Pop( &pJob ); + if ( pfnFilter && !(*pfnFilter)( pJob ) ) + { + if ( pJob->CanExecute() ) + { + jobsToPutBack.EnsureCapacity( nJobsTotal ); + jobsToPutBack.AddToTail( pJob ); + } + else + { + m_nJobs--; + pJob->Release(); // an already serviced job in queue, may as well ditch it (as in, main thread probably force executed) + } + continue; + } + ServiceJobAndRelease( pJob ); + m_nJobs--; + nExecuted++; + } + + } + + while ( m_SharedQueue.Count( (JobPriority_t)iCurPriority ) ) + { + m_SharedQueue.Pop( &pJob ); + if ( pfnFilter && !(*pfnFilter)( pJob ) ) + { + if ( pJob->CanExecute() ) + { + jobsToPutBack.EnsureCapacity( nJobsTotal ); + jobsToPutBack.AddToTail( pJob ); + } + else + { + m_nJobs--; + pJob->Release(); // see above + } + continue; + } + + ServiceJobAndRelease( pJob ); + m_nJobs--; + nExecuted++; + } + } + + for ( i = 0; i < jobsToPutBack.Count(); i++ ) + { + InsertJobInQueue( jobsToPutBack[i] ); + jobsToPutBack[i]->Release(); + } + + ResumeExecution(); + + return nExecuted; +} + +//--------------------------------------------------------- +// +//--------------------------------------------------------- + +int CThreadPool::AbortAll() +{ + SuspendExecution(); + CJob *pJob; + + int iAborted = 0; + while ( m_SharedQueue.Pop( &pJob ) ) + { + pJob->Abort(); + pJob->Release(); + iAborted++; + } + + for ( int i = 0; i < m_Threads.Count(); i++ ) + { + CJobQueue &queue = m_Threads[i]->AccessDirectQueue(); + while ( queue.Pop( &pJob ) ) + { + pJob->Abort(); + pJob->Release(); + iAborted++; + } + + } + + m_nJobs = 0; + + ResumeExecution(); + + return iAborted; +} + +//--------------------------------------------------------- +// CThreadPool thread functions +//--------------------------------------------------------- + +bool CThreadPool::Start( const ThreadPoolStartParams_t &startParams, const char *pszName ) +{ + int nThreads = startParams.nThreads; + + m_bExecOnThreadPoolThreadsOnly = startParams.bExecOnThreadPoolThreadsOnly; + + if ( nThreads < 0 ) + { + const CPUInformation &ci = *GetCPUInformation(); + if ( startParams.bIOThreads ) + { + nThreads = ci.m_nLogicalProcessors; + } + else + { + nThreads = ( ci.m_nLogicalProcessors / (( ci.m_bHT ) ? 2 : 1) ) - 1; // One per + if ( IsPC() ) + { + if ( nThreads > 3 ) + { + DevMsg( "Defaulting to limit of 3 worker threads, use -threads on command line if want more\n" ); // Current >4 processor configs don't really work so well, probably due to cache issues? (toml 7/12/2007) + nThreads = 3; + } + } + } + + if ( ( startParams.nThreadsMax >= 0 ) && ( nThreads > startParams.nThreadsMax ) ) + { + nThreads = startParams.nThreadsMax; + } + } + + if ( nThreads <= 0 ) + { + return true; + } + + int nStackSize = startParams.nStackSize; + + if ( nStackSize < 0 ) + { + if ( startParams.bIOThreads ) + { + nStackSize = IO_STACKSIZE; + } + else + { + nStackSize = COMPUTATION_STACKSIZE; + } + } + + int priority = startParams.iThreadPriority; + + if ( priority == SHRT_MIN ) + { + if ( startParams.bIOThreads ) + { + priority = THREAD_PRIORITY_HIGHEST; + } + else + { + priority = ThreadGetPriority(); + } + } + + bool bDistribute; + if ( startParams.fDistribute != TRS_NONE ) + { + bDistribute = ( startParams.fDistribute == TRS_TRUE ); + } + else + { + bDistribute = !startParams.bIOThreads; + } + + //-------------------------------------------------------- + + m_Threads.EnsureCapacity( nThreads ); + m_IdleEvents.EnsureCapacity( nThreads ); + + if ( !pszName ) + { + pszName = ( startParams.bIOThreads ) ? "IOJobX" : "CmpJobX"; + } + while ( nThreads-- ) + { + int iThread = m_Threads.AddToTail(); + m_IdleEvents.AddToTail(); + m_Threads[iThread] = new CJobThread( this, iThread ); + m_IdleEvents[iThread] = &m_Threads[iThread]->GetIdleEvent(); + m_Threads[iThread]->SetName( CFmtStr( "%s%d", pszName, iThread ) ); + m_Threads[iThread]->Start( nStackSize ); + m_Threads[iThread]->GetIdleEvent().Wait(); +#ifdef WIN32 + ThreadSetPriority( (ThreadHandle_t)m_Threads[iThread]->GetThreadHandle(), priority ); +#endif + } + + Distribute( bDistribute, startParams.bUseAffinityTable ? (int *)startParams.iAffinityTable : NULL ); + + return true; +} + +//--------------------------------------------------------- + +void CThreadPool::Distribute( bool bDistribute, int *pAffinityTable ) +{ + if ( bDistribute ) + { + const CPUInformation &ci = *GetCPUInformation(); + int nHwThreadsPer = (( ci.m_bHT ) ? 2 : 1); + if ( ci.m_nLogicalProcessors > 1 ) + { + if ( !pAffinityTable ) + { +#if defined( IS_WINDOWS_PC ) + // no affinity table, distribution is cycled across all available + HINSTANCE hInst = LoadLibrary( "kernel32.dll" ); + if ( hInst ) + { + typedef DWORD (WINAPI *SetThreadIdealProcessorFn)(ThreadHandle_t hThread, DWORD dwIdealProcessor); + SetThreadIdealProcessorFn Thread_SetIdealProcessor = (SetThreadIdealProcessorFn)GetProcAddress( hInst, "SetThreadIdealProcessor" ); + if ( Thread_SetIdealProcessor ) + { + ThreadHandle_t hMainThread = ThreadGetCurrentHandle(); + Thread_SetIdealProcessor( hMainThread, 0 ); + int iProc = 0; + for ( int i = 0; i < m_Threads.Count(); i++ ) + { + iProc += nHwThreadsPer; + if ( iProc >= ci.m_nLogicalProcessors ) + { + iProc %= ci.m_nLogicalProcessors; + if ( nHwThreadsPer > 1 ) + { + iProc = ( iProc + 1 ) % nHwThreadsPer; + } + } + Thread_SetIdealProcessor((ThreadHandle_t)m_Threads[i]->GetThreadHandle(), iProc); + } + } + FreeLibrary( hInst ); + } +#else + // no affinity table, distribution is cycled across all available + int iProc = 0; + for ( int i = 0; i < m_Threads.Count(); i++ ) + { + iProc += nHwThreadsPer; + if ( iProc >= ci.m_nLogicalProcessors ) + { + iProc %= ci.m_nLogicalProcessors; + if ( nHwThreadsPer > 1 ) + { + iProc = ( iProc + 1 ) % nHwThreadsPer; + } + } +#ifdef WIN32 + ThreadSetAffinity( (ThreadHandle_t)m_Threads[i]->GetThreadHandle(), 1 << iProc ); +#endif + } +#endif + } + else + { + // distribution is from affinity table + for ( int i = 0; i < m_Threads.Count(); i++ ) + { +#ifdef WIN32 + ThreadSetAffinity( (ThreadHandle_t)m_Threads[i]->GetThreadHandle(), pAffinityTable[i] ); +#endif + } + } + } + } + else + { +#ifdef WIN32 + DWORD_PTR dwProcessAffinity, dwSystemAffinity; + if ( GetProcessAffinityMask( GetCurrentProcess(), &dwProcessAffinity, &dwSystemAffinity ) ) + { + for ( int i = 0; i < m_Threads.Count(); i++ ) + { + ThreadSetAffinity( (ThreadHandle_t)m_Threads[i]->GetThreadHandle(), dwProcessAffinity ); + } + } +#endif + } +} + +//--------------------------------------------------------- + +bool CThreadPool::Stop( int timeout ) +{ + for ( int i = 0; i < m_Threads.Count(); i++ ) + { + m_Threads[i]->CallWorker( TPM_EXIT ); + } + + for ( int i = 0; i < m_Threads.Count(); ++i ) + { + while( m_Threads[i]->IsAlive() ) + { + ThreadSleep( 0 ); + } + delete m_Threads[i]; + } + + m_nJobs = 0; + m_SharedQueue.Flush(); + m_nIdleThreads = 0; + m_Threads.RemoveAll(); + m_IdleEvents.RemoveAll(); + + return true; +} + +//--------------------------------------------------------- + +CJob *CThreadPool::GetDummyJob() +{ + class CDummyJob : public CJob + { + public: + CDummyJob() + { + Execute(); + } + + virtual JobStatus_t DoExecute() { return JOB_OK; } + }; + + static CDummyJob dummyJob; + + dummyJob.AddRef(); + return &dummyJob; +} + +//----------------------------------------------------------------------------- + + +namespace ThreadPoolTest +{ +int g_iSleep; + +CThreadEvent g_done; +int g_nTotalToComplete; +CThreadPool *g_pTestThreadPool; + +class CCountJob : public CJob +{ +public: + virtual JobStatus_t DoExecute() + { + m_nCount++; + ThreadPause(); + if ( g_iSleep >= 0) + ThreadSleep( g_iSleep ); + if ( bDoWork ) + { + byte pMemory[1024]; + int i; + for ( i = 0; i < 1024; i++ ) + { + pMemory[i] = rand(); + } + for ( i = 0; i < 50; i++ ) + { + sqrt( (float)HashBlock( pMemory, 1024 ) + HashBlock( pMemory, 1024 ) + 10.0 ); + } + bDoWork = false; + } + if ( m_nCount == g_nTotalToComplete ) + g_done.Set(); + return 0; + } + + static CInterlockedInt m_nCount; + bool bDoWork; +}; +CInterlockedInt CCountJob::m_nCount; +int g_nTotalAtFinish; + +void Test( bool bDistribute, bool bSleep = true, bool bFinishExecute = false, bool bDoWork = false ) +{ + for ( int bInterleavePushPop = 0; bInterleavePushPop < 2; bInterleavePushPop++ ) + { + for ( g_iSleep = -10; g_iSleep <= 10; g_iSleep += 10 ) + { + Msg( "ThreadPoolTest: Testing! Sleep %d, interleave %d \n", g_iSleep, bInterleavePushPop ); + int nMaxThreads = ( IsX360() ) ? 6 : 8; + int nIncrement = ( IsX360() ) ? 1 : 2; + for ( int i = 1; i <= nMaxThreads; i += nIncrement ) + { + CCountJob::m_nCount = 0; + g_nTotalAtFinish = 0; + ThreadPoolStartParams_t params; + params.nThreads = i; + params.fDistribute = ( bDistribute) ? TRS_TRUE : TRS_FALSE; + g_pTestThreadPool->Start( params, "Tst" ); + if ( !bInterleavePushPop ) + { + g_pTestThreadPool->SuspendExecution(); + } + + CCountJob jobs[4000]; + g_nTotalToComplete = ARRAYSIZE(jobs); + + CFastTimer timer, suspendTimer; + + suspendTimer.Start(); + timer.Start(); + for ( int j = 0; j < ARRAYSIZE(jobs); j++ ) + { + jobs[j].SetFlags( JF_QUEUE ); + jobs[j].bDoWork = bDoWork; + g_pTestThreadPool->AddJob( &jobs[j] ); + if ( bSleep && j % 16 == 0 ) + { + ThreadSleep( 0 ); + } + } + if ( !bInterleavePushPop ) + { + g_pTestThreadPool->ResumeExecution(); + } + if ( bFinishExecute && g_iSleep <= 1 ) + { + g_done.Wait(); + } + g_nTotalAtFinish = CCountJob::m_nCount; + timer.End(); + g_pTestThreadPool->SuspendExecution(); + suspendTimer.End(); + g_pTestThreadPool->ResumeExecution(); + g_pTestThreadPool->Stop(); + g_done.Reset(); + + int counts[8] = { 0 }; + for ( int j = 0; j < ARRAYSIZE(jobs); j++ ) + { + if ( jobs[j].GetServiceThread() != -1 ) + { + counts[jobs[j].GetServiceThread()]++; + jobs[j].ClearServiceThread(); + } + } + + Msg( "ThreadPoolTest: %d threads -- %d (%d) jobs processed in %fms, %fms to suspend (%f/%f) [%d, %d, %d, %d, %d, %d, %d, %d]\n", + i, g_nTotalAtFinish, (int)CCountJob::m_nCount, timer.GetDuration().GetMillisecondsF(), suspendTimer.GetDuration().GetMillisecondsF() - timer.GetDuration().GetMillisecondsF(), + timer.GetDuration().GetMillisecondsF() / (float)CCountJob::m_nCount, (suspendTimer.GetDuration().GetMillisecondsF())/(float)g_nTotalAtFinish, + counts[0], counts[1], counts[2], counts[3], counts[4], counts[5], counts[6], counts[7] ); + } + } + } +} + + +bool g_bOutputError; +volatile int g_ReadyToExecute; +CInterlockedInt g_nReady; + +class CExecuteTestJob : public CJob +{ +public: + virtual JobStatus_t DoExecute() + { + byte pMemory[1024]; + int i; + for ( i = 0; i < 1024; i++ ) + { + pMemory[i] = rand(); + } + for ( i = 0; i < 50; i++ ) + { + sqrt( (float)HashBlock( pMemory, 1024 ) + HashBlock( pMemory, 1024 ) + 10.0 ); + } + if ( AccessEvent()->Check() || IsFinished() ) + { + if ( !g_bOutputError ) + { + Msg( "Forced execute test failed!\n" ); + DebuggerBreakIfDebugging(); + } + } + return 0; + } +}; + +class CExecuteTestExecuteJob : public CJob +{ +public: + virtual JobStatus_t DoExecute() + { + bool bAbort = ( RandomInt( 1, 10 ) == 1 ); + g_nReady++; + while ( !g_ReadyToExecute ) + { + ThreadPause(); + } + + if ( !bAbort ) + m_pTestJob->Execute(); + else + m_pTestJob->Abort(); + g_nReady--; + return 0; + } + + CExecuteTestJob *m_pTestJob; +}; + + +void TestForcedExecute() +{ + Msg( "TestForcedExecute\n" ); + for ( int tests = 0; tests < 30; tests++ ) + { + for ( int i = 1; i <= 5; i += 2 ) + { + g_nReady = 0; + ThreadPoolStartParams_t params; + params.nThreads = i; + params.fDistribute = TRS_TRUE; + g_pTestThreadPool->Start( params, "Tst" ); + + static CExecuteTestJob jobs[4000]; + for ( int j = 0; j < ARRAYSIZE(jobs); j++ ) + { + g_ReadyToExecute = false; + for ( int k = 0; k < i; k++ ) + { + CExecuteTestExecuteJob *pJob = new CExecuteTestExecuteJob; + pJob->SetFlags( JF_QUEUE ); + pJob->m_pTestJob = &jobs[j]; + g_pTestThreadPool->AddJob( pJob ); + pJob->Release(); + } + while ( g_nReady < i ) + { + ThreadPause(); + } + g_ReadyToExecute = true; + ThreadSleep(); + jobs[j].Execute(); + while ( g_nReady > 0 ) + { + ThreadPause(); + } + } + g_pTestThreadPool->Stop(); + } + } + Msg( "TestForcedExecute DONE\n" ); +} + +} // namespace ThreadPoolTest + +void RunThreadPoolTests() +{ + CThreadPool pool; + ThreadPoolTest::g_pTestThreadPool = &pool; + RunTSQueueTests(10000); + RunTSListTests(10000); + +#ifdef _WIN32 + DWORD_PTR mask1 = 0; + --mask1; + DWORD_PTR mask2 = 0; + --mask2; + GetProcessAffinityMask( GetCurrentProcess(), &mask1, &mask2 ); +#else + int32 mask1=-1; +#endif + Msg( "ThreadPoolTest: Job distribution speed\n" ); + for ( int i = 0; i < 2; i++ ) + { + bool bToCompletion = ( i % 2 != 0 ); + if ( !IsX360() ) + { + Msg( "ThreadPoolTest: Non-distribute\n" ); + ThreadPoolTest::Test( false, true, bToCompletion ); + } + + Msg( "ThreadPoolTest: Distribute\n" ); + ThreadPoolTest::Test( true, true, bToCompletion ); + + Msg( "ThreadPoolTest: One core\n" ); + ThreadSetAffinity( 0, 1 ); + ThreadPoolTest::Test( false, true, bToCompletion ); + ThreadSetAffinity( 0, mask1 ); + + Msg( "ThreadPoolTest: NO Sleep\n" ); + ThreadPoolTest::Test( false, false, bToCompletion ); + + Msg( "ThreadPoolTest: Distribute\n" ); + ThreadPoolTest::Test( true, false, bToCompletion ); + + Msg( "ThreadPoolTest: One core\n" ); + ThreadSetAffinity( 0, 1 ); + ThreadPoolTest::Test( false, false, bToCompletion ); + ThreadSetAffinity( 0, mask1 ); + } + + Msg( "ThreadPoolTest: Jobs doing work\n" ); + for ( int i = 0; i < 2; i++ ) + { + bool bToCompletion = true;// = ( i % 2 != 0 ); + if ( !IsX360() ) + { + Msg( "ThreadPoolTest: Non-distribute\n" ); + ThreadPoolTest::Test( false, true, bToCompletion, true ); + } + + Msg( "ThreadPoolTest: Distribute\n" ); + ThreadPoolTest::Test( true, true, bToCompletion, true ); + + Msg( "ThreadPoolTest: One core\n" ); + ThreadSetAffinity( 0, 1 ); + ThreadPoolTest::Test( false, true, bToCompletion, true ); + ThreadSetAffinity( 0, mask1 ); + + Msg( "ThreadPoolTest: NO Sleep\n" ); + ThreadPoolTest::Test( false, false, bToCompletion, true ); + + Msg( "ThreadPoolTest: Distribute\n" ); + ThreadPoolTest::Test( true, false, bToCompletion, true ); + + Msg( "ThreadPoolTest: One core\n" ); + ThreadSetAffinity( 0, 1 ); + ThreadPoolTest::Test( false, false, bToCompletion, true ); + ThreadSetAffinity( 0, mask1 ); + } +#ifdef _WIN32 + GetProcessAffinityMask( GetCurrentProcess(), &mask1, &mask2 ); +#endif + + ThreadPoolTest::TestForcedExecute(); +} diff --git a/vstdlib/osversion.cpp b/vstdlib/osversion.cpp new file mode 100644 index 0000000..6ed8e1f --- /dev/null +++ b/vstdlib/osversion.cpp @@ -0,0 +1,427 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "vstdlib/osversion.h" +#include "winlite.h" +#include "strtools.h" +#include "tier0/dbg.h" + +#ifdef OSX +#include <CoreServices/CoreServices.h> +#endif + +//----------------------------------------------------------------------------- +// Purpose: return the OS type for this machine +//----------------------------------------------------------------------------- +EOSType GetOSType() +{ + static EOSType eOSVersion = k_eOSUnknown; + +#if defined( _WIN32 ) && !defined( _X360 ) + if ( eOSVersion == k_eOSUnknown || eOSVersion == k_eWinUnknown ) + { + eOSVersion = k_eWinUnknown; + OSVERSIONINFOEX osvi; + Q_memset( &osvi, 0x00, sizeof(osvi) ); + osvi.dwOSVersionInfoSize = sizeof(osvi); + + if ( GetVersionEx( (OSVERSIONINFO *) &osvi ) ) + { + switch ( osvi.dwPlatformId ) + { + case VER_PLATFORM_WIN32_NT: + if ( osvi.dwMajorVersion <= 4 ) + { + eOSVersion = k_eWinNT; + } + else if ( osvi.dwMajorVersion == 5 ) + { + switch( osvi.dwMinorVersion ) + { + case 0: + eOSVersion = k_eWin2000; + break; + case 1: + eOSVersion = k_eWinXP; + break; + case 2: + eOSVersion = k_eWin2003; + break; + } + } + else if ( osvi.dwMajorVersion >= 6 ) + { + if ( osvi.wProductType == VER_NT_WORKSTATION ) + { + switch ( osvi.dwMinorVersion ) + { + case 0: + eOSVersion = k_eWinVista; + break; + case 1: + eOSVersion = k_eWindows7; + break; + } + } + else /* ( osvi.wProductType != VER_NT_WORKSTATION ) */ + { + switch ( osvi.dwMinorVersion ) + { + case 0: + eOSVersion = k_eWin2008; // Windows 2008, not R2 + break; + case 1: + eOSVersion = k_eWin2008; // Windows 2008 R2 + break; + } + } + } + break; + case VER_PLATFORM_WIN32_WINDOWS: + switch ( osvi.dwMinorVersion ) + { + case 0: + eOSVersion = k_eWin95; + break; + case 10: + eOSVersion = k_eWin98; + break; + case 90: + eOSVersion = k_eWinME; + break; + } + break; + case VER_PLATFORM_WIN32s: + eOSVersion = k_eWin311; + break; + } + } + } +#elif defined(OSX) + if ( eOSVersion == k_eOSUnknown ) + { + SInt32 MajorVer = 0; + SInt32 MinorVer = 0; + SInt32 PatchVer = 0; + OSErr err = noErr; + err = Gestalt( gestaltSystemVersionMajor, &MajorVer ); + if ( err != noErr ) + return k_eOSUnknown; + err = Gestalt( gestaltSystemVersionMinor, &MinorVer ); + if ( err != noErr ) + return k_eOSUnknown; + err = Gestalt( gestaltSystemVersionBugFix, &PatchVer ); + if ( err != noErr ) + return k_eOSUnknown; + + switch ( MajorVer ) + { + case 10: + { + switch( MinorVer ) + { + case 4: + eOSVersion = k_eMacOS104; + break; + case 5: + eOSVersion = k_eMacOS105; + switch ( PatchVer ) + { + case 8: + eOSVersion = k_eMacOS1058; + default: + break; + } + break; + case 6: + eOSVersion = k_eMacOS106; + switch ( PatchVer ) + { + case 1: + case 2: + break; + case 3: + default: + // note the default here - 10.6.4 (5,6...) >= 10.6.3, so we want to + // identify as 10.6.3 for sysreqs purposes + eOSVersion = k_eMacOS1063; + break; + } + break; + case 7: + eOSVersion = k_eMacOS107; + break; + default: + break; + } + } + default: + break; + } + } +#elif defined(LINUX) + if ( eOSVersion == k_eOSUnknown ) + { + + FILE *fpKernelVer = fopen( "/proc/version", "r" ); + + if ( !fpKernelVer ) + return k_eLinuxUnknown; + + char rgchVersionLine[1024]; + char *pchRet = fgets( rgchVersionLine, sizeof(rgchVersionLine), fpKernelVer ); + fclose( fpKernelVer ); + + eOSVersion = k_eLinuxUnknown; + + // move past "Linux version " + const char *pchVersion = rgchVersionLine + Q_strlen( "Linux version " ); + if ( pchRet && *pchVersion == '2' && *(pchVersion+1) == '.' ) + { + pchVersion += 2; // move past "2." + if ( *pchVersion == '2' && *(pchVersion+1) == '.' ) + eOSVersion = k_eLinux22; + else if ( *pchVersion == '4' && *(pchVersion+1) == '.' ) + eOSVersion = k_eLinux24; + else if ( *pchVersion == '6' && *(pchVersion+1) == '.' ) + eOSVersion = k_eLinux26; + } + } +#endif + return eOSVersion; +} + +//----------------------------------------------------------------------------- +// Purpose: get platform-specific OS details (distro, on linux) +// returns a pointer to the input buffer on success (for convenience), +// NULL on failure. +//----------------------------------------------------------------------------- +const char *GetOSDetailString( char *pchOutBuf, int cchOutBuf ) +{ +#if defined WIN32 + (void)( pchOutBuf ); + (void)( cchOutBuf ); + // no interesting details + return NULL; +#else +#if defined LINUX + // we're about to go poking around to see if we can figure out distribution + // looking @ any /etc file is fragile (people can change 'em), + // but since this is just hardware survey data, we're not super concerned. + // a bunch of OS-specific issue files + const char *pszIssueFile[] = + { + "/etc/redhat-release", + "/etc/fedora-release", + "/etc/slackware-release", + "/etc/debian_release", + "/etc/mandrake-release", + "/etc/yellowdog-release", + "/etc/gentoo-release", + "/etc/lsb-release", + "/etc/SUSE-release", + }; + if ( !pchOutBuf ) + return NULL; + + for (int i = 0; i < Q_ARRAYSIZE( pszIssueFile ); i++ ) + { + FILE *fdInfo = fopen( pszIssueFile[i], "r" ); + if ( !fdInfo ) + continue; + + // prepend the buffer with the name of the file we found for easier grouping + snprintf( pchOutBuf, cchOutBuf, "%s\n", pszIssueFile[i] ); + int cchIssueFile = strlen( pszIssueFile[i] ) + 1; + ssize_t cubRead = fread( (void*) (pchOutBuf + cchIssueFile) , sizeof(char), cchOutBuf - cchIssueFile, fdInfo ); + fclose( fdInfo ); + + if ( cubRead < 0 ) + return NULL; + + // null terminate + pchOutBuf[ MIN( cubRead, cchOutBuf-1 ) ] = '\0'; + return pchOutBuf; + } +#endif + // if all else fails, just send back uname -a + if ( !pchOutBuf ) + return NULL; + FILE *fpUname = popen( "uname -mrsv", "r" ); + if ( !fpUname ) + return NULL; + size_t cchRead = fread( pchOutBuf, sizeof(char), cchOutBuf, fpUname ); + pclose( fpUname ); + + pchOutBuf[ MIN( cchRead, (size_t)cchOutBuf-1 ) ] = '\0'; + return pchOutBuf; +#endif +} + + +//----------------------------------------------------------------------------- +// Purpose: get a friendly name for an OS type +//----------------------------------------------------------------------------- +const char *GetNameFromOSType( EOSType eOSType ) +{ + switch ( eOSType ) + { + case k_eWinUnknown: + return "Windows"; + case k_eWin311: + return "Windows 3.11"; + case k_eWin95: + return "Windows 95"; + case k_eWin98: + return "Windows 98"; + case k_eWinME: + return "Windows ME"; + case k_eWinNT: + return "Windows NT"; + case k_eWin2000: + return "Windows 2000"; + case k_eWinXP: + return "Windows XP"; + case k_eWin2003: + return "Windows 2003"; + case k_eWinVista: + return "Windows Vista"; + case k_eWindows7: + return "Windows 7"; + case k_eWin2008: + return "Windows 2008"; +#ifdef POSIX + case k_eMacOSUnknown: + return "Mac OS"; + case k_eMacOS104: + return "MacOS 10.4"; + case k_eMacOS105: + return "MacOS 10.5"; + case k_eMacOS1058: + return "MacOS 10.5.8"; + case k_eMacOS106: + return "MacOS 10.6"; + case k_eMacOS1063: + return "MacOS 10.6.3"; + case k_eMacOS107: + return "MacOS 10.7"; + case k_eLinuxUnknown: + return "Linux"; + case k_eLinux22: + return "Linux 2.2"; + case k_eLinux24: + return "Linux 2.4"; + case k_eLinux26: + return "Linux 2.6"; +#endif + default: + case k_eOSUnknown: + return "Unknown"; + } +} + + +// friendly name to OS type, MUST be same size as EOSType enum +struct OSTypeNameTuple +{ + EOSType m_OSType; + const char *m_pchOSName; + +}; + +const OSTypeNameTuple k_rgOSTypeToName[] = +{ + { k_eOSUnknown, "unknown" }, + { k_eMacOSUnknown, "macos" }, + { k_eMacOS104, "macos104" }, + { k_eMacOS105, "macos105" }, + { k_eMacOS1058, "macos1058" }, + { k_eMacOS106, "macos106" }, + { k_eMacOS1063, "macos1063" }, + { k_eMacOS107, "macos107" }, + { k_eLinuxUnknown, "linux" }, + { k_eLinux22, "linux22" }, + { k_eLinux24, "linux24" }, + { k_eLinux26, "linux26" }, + { k_eWinUnknown, "windows" }, + { k_eWin311, "win311" }, + { k_eWin95, "win95" }, + { k_eWin98, "win98" }, + { k_eWinME, "winME" }, + { k_eWinNT, "winNT" }, + { k_eWin2000, "win200" }, + { k_eWinXP, "winXP" }, + { k_eWin2003, "win2003" }, + { k_eWinVista, "winVista" }, + { k_eWindows7, "win7" }, + { k_eWin2008, "win2008" }, +}; + +//----------------------------------------------------------------------------- +// Purpose: convert a friendly OS name to a eostype +//----------------------------------------------------------------------------- +EOSType GetOSTypeFromString_Deprecated( const char *pchName ) +{ + EOSType eOSType; +#ifdef WIN32 + eOSType = k_eWinUnknown; +#else + eOSType = k_eOSUnknown; +#endif + + // if this fires, make sure all OS types are in the map + Assert( Q_ARRAYSIZE( k_rgOSTypeToName ) == k_eOSTypeMax ); + if ( !pchName || Q_strlen( pchName ) == 0 ) + return eOSType; + + for ( int iOS = 0; iOS < Q_ARRAYSIZE( k_rgOSTypeToName ) ; iOS++ ) + { + if ( !Q_stricmp( k_rgOSTypeToName[iOS].m_pchOSName, pchName ) ) + return k_rgOSTypeToName[iOS].m_OSType; + } + return eOSType; +} + +bool OSTypesAreCompatible( EOSType eOSTypeDetected, EOSType eOSTypeRequired ) +{ + // check windows (on the positive side of the number line) + if ( eOSTypeRequired >= k_eWinUnknown ) + return ( eOSTypeDetected >= eOSTypeRequired ); + + if ( eOSTypeRequired == k_eOSUnknown ) + return true; + + // osx + if ( eOSTypeRequired >= k_eMacOSUnknown && eOSTypeRequired < k_eOSUnknown ) + return ( eOSTypeDetected >= eOSTypeRequired && eOSTypeDetected < k_eOSUnknown ); + + // and linux + if ( eOSTypeRequired >= k_eLinuxUnknown && eOSTypeRequired < k_eMacOSUnknown ) + return ( eOSTypeDetected >= eOSTypeRequired && eOSTypeDetected < k_eMacOSUnknown ); + + return false; +} + +// these strings "windows", "macos", "linux" are part of the +// interface, which is why they're hard-coded here, rather than using +// the strings in k_rgOSTypeToName +const char *GetPlatformName( bool *pbIs64Bit ) +{ + if ( pbIs64Bit ) + *pbIs64Bit = Is64BitOS(); + + EOSType eType = GetOSType(); + if ( OSTypesAreCompatible( eType, k_eWinUnknown ) ) + return "windows"; + if ( OSTypesAreCompatible( eType, k_eMacOSUnknown ) ) + return "macos"; + if ( OSTypesAreCompatible( eType, k_eLinuxUnknown ) ) + return "linux"; + return "unknown"; +} + diff --git a/vstdlib/processutils.cpp b/vstdlib/processutils.cpp new file mode 100644 index 0000000..dc4ee59 --- /dev/null +++ b/vstdlib/processutils.cpp @@ -0,0 +1,473 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//===========================================================================// + +#if !defined( _X360 ) +#include <windows.h> +#endif +#include "vstdlib/iprocessutils.h" +#include "tier1/utllinkedlist.h" +#include "tier1/utlstring.h" +#include "tier1/utlbuffer.h" +#include "tier1/tier1.h" + +//----------------------------------------------------------------------------- +// At the moment, we can only run one process at a time +//----------------------------------------------------------------------------- +class CProcessUtils : public CTier1AppSystem< IProcessUtils > +{ + typedef CTier1AppSystem< IProcessUtils > BaseClass; + +public: + CProcessUtils() : BaseClass( false ) {} + + // Inherited from IAppSystem + virtual InitReturnVal_t Init(); + virtual void Shutdown(); + + // Inherited from IProcessUtils + virtual ProcessHandle_t StartProcess( const char *pCommandLine, bool bConnectStdPipes ); + virtual ProcessHandle_t StartProcess( int argc, const char **argv, bool bConnectStdPipes ); + virtual void CloseProcess( ProcessHandle_t hProcess ); + virtual void AbortProcess( ProcessHandle_t hProcess ); + virtual bool IsProcessComplete( ProcessHandle_t hProcess ); + virtual void WaitUntilProcessCompletes( ProcessHandle_t hProcess ); + virtual int SendProcessInput( ProcessHandle_t hProcess, char *pBuf, int nBufLen ); + virtual int GetProcessOutputSize( ProcessHandle_t hProcess ); + virtual int GetProcessOutput( ProcessHandle_t hProcess, char *pBuf, int nBufLen ); + virtual int GetProcessExitCode( ProcessHandle_t hProcess ); + +private: + struct ProcessInfo_t + { + HANDLE m_hChildStdinRd; + HANDLE m_hChildStdinWr; + HANDLE m_hChildStdoutRd; + HANDLE m_hChildStdoutWr; + HANDLE m_hChildStderrWr; + HANDLE m_hProcess; + CUtlString m_CommandLine; + CUtlBuffer m_ProcessOutput; + }; + + // Returns the last error that occurred + char *GetErrorString( char *pBuf, int nBufLen ); + + // creates the process, adds it to the list and writes the windows HANDLE into info.m_hProcess + ProcessHandle_t CreateProcess( ProcessInfo_t &info, bool bConnectStdPipes ); + + // Shuts down the process handle + void ShutdownProcess( ProcessHandle_t hProcess ); + + // Methods used to read output back from a process + int GetActualProcessOutputSize( ProcessHandle_t hProcess ); + int GetActualProcessOutput( ProcessHandle_t hProcess, char *pBuf, int nBufLen ); + + CUtlFixedLinkedList< ProcessInfo_t > m_Processes; + ProcessHandle_t m_hCurrentProcess; + bool m_bInitialized; +}; + + +//----------------------------------------------------------------------------- +// Purpose: singleton accessor +//----------------------------------------------------------------------------- +static CProcessUtils s_ProcessUtils; +EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CProcessUtils, IProcessUtils, PROCESS_UTILS_INTERFACE_VERSION, s_ProcessUtils ); + + +//----------------------------------------------------------------------------- +// Initialize, shutdown process system +//----------------------------------------------------------------------------- +InitReturnVal_t CProcessUtils::Init() +{ + InitReturnVal_t nRetVal = BaseClass::Init(); + if ( nRetVal != INIT_OK ) + return nRetVal; + + m_bInitialized = true; + m_hCurrentProcess = PROCESS_HANDLE_INVALID; + return INIT_OK; +} + +void CProcessUtils::Shutdown() +{ + Assert( m_bInitialized ); + Assert( m_Processes.Count() == 0 ); + if ( m_Processes.Count() != 0 ) + { + AbortProcess( m_hCurrentProcess ); + } + m_bInitialized = false; + return BaseClass::Shutdown(); +} + + +//----------------------------------------------------------------------------- +// Returns the last error that occurred +//----------------------------------------------------------------------------- +char *CProcessUtils::GetErrorString( char *pBuf, int nBufLen ) +{ + FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), 0, pBuf, nBufLen, NULL ); + char *p = strchr(pBuf, '\r'); // get rid of \r\n + if(p) + { + p[0] = 0; + } + return pBuf; +} + + +ProcessHandle_t CProcessUtils::CreateProcess( ProcessInfo_t &info, bool bConnectStdPipes ) +{ + STARTUPINFO si; + memset(&si, 0, sizeof si); + si.cb = sizeof(si); + if ( bConnectStdPipes ) + { + si.dwFlags = STARTF_USESTDHANDLES; + si.hStdInput = info.m_hChildStdinRd; + si.hStdError = info.m_hChildStderrWr; + si.hStdOutput = info.m_hChildStdoutWr; + } + + PROCESS_INFORMATION pi; + if ( ::CreateProcess( NULL, info.m_CommandLine.GetForModify(), NULL, NULL, TRUE, DETACHED_PROCESS, NULL, NULL, &si, &pi ) ) + { + info.m_hProcess = pi.hProcess; + m_hCurrentProcess = m_Processes.AddToTail( info ); + return m_hCurrentProcess; + } + + char buf[ 512 ]; + Warning( "Could not execute the command:\n %s\n" + "Windows gave the error message:\n \"%s\"\n", + info.m_CommandLine.Get(), GetErrorString( buf, sizeof(buf) ) ); + + return PROCESS_HANDLE_INVALID; +} + +//----------------------------------------------------------------------------- +// Options for compilation +//----------------------------------------------------------------------------- +ProcessHandle_t CProcessUtils::StartProcess( const char *pCommandLine, bool bConnectStdPipes ) +{ + Assert( m_bInitialized ); + + // NOTE: For the moment, we can only run one process at a time + // although in the future, I expect to have a process queue. + if ( m_hCurrentProcess != PROCESS_HANDLE_INVALID ) + { + WaitUntilProcessCompletes( m_hCurrentProcess ); + } + + ProcessInfo_t info; + info.m_CommandLine = pCommandLine; + + if ( !bConnectStdPipes ) + { + info.m_hChildStderrWr = INVALID_HANDLE_VALUE; + info.m_hChildStdinRd = info.m_hChildStdinWr = INVALID_HANDLE_VALUE; + info.m_hChildStdoutRd = info.m_hChildStdoutWr = INVALID_HANDLE_VALUE; + + return CreateProcess( info, false ); + } + + SECURITY_ATTRIBUTES saAttr; + + // Set the bInheritHandle flag so pipe handles are inherited. + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; + saAttr.lpSecurityDescriptor = NULL; + + // Create a pipe for the child's STDOUT. + if ( CreatePipe( &info.m_hChildStdoutRd, &info.m_hChildStdoutWr, &saAttr, 0 ) ) + { + if ( CreatePipe( &info.m_hChildStdinRd, &info.m_hChildStdinWr, &saAttr, 0 ) ) + { + if ( DuplicateHandle( GetCurrentProcess(), info.m_hChildStdoutWr, GetCurrentProcess(), + &info.m_hChildStderrWr, 0, TRUE, DUPLICATE_SAME_ACCESS ) ) + { +// _setmode( info.m_hChildStdoutRd, _O_TEXT ); +// _setmode( info.m_hChildStdoutWr, _O_TEXT ); +// _setmode( info.m_hChildStderrWr, _O_TEXT ); + + ProcessHandle_t hProcess = CreateProcess( info, true ); + if ( hProcess != PROCESS_HANDLE_INVALID ) + return hProcess; + + CloseHandle( info.m_hChildStderrWr ); + } + CloseHandle( info.m_hChildStdinRd ); + CloseHandle( info.m_hChildStdinWr ); + } + CloseHandle( info.m_hChildStdoutRd ); + CloseHandle( info.m_hChildStdoutWr ); + } + return PROCESS_HANDLE_INVALID; +} + + +//----------------------------------------------------------------------------- +// Start up a process +//----------------------------------------------------------------------------- +ProcessHandle_t CProcessUtils::StartProcess( int argc, const char **argv, bool bConnectStdPipes ) +{ + CUtlString commandLine; + for ( int i = 0; i < argc; ++i ) + { + commandLine += argv[i]; + if ( i != argc-1 ) + { + commandLine += " "; + } + } + return StartProcess( commandLine.Get(), bConnectStdPipes ); +} + + +//----------------------------------------------------------------------------- +// Shuts down the process handle +//----------------------------------------------------------------------------- +void CProcessUtils::ShutdownProcess( ProcessHandle_t hProcess ) +{ + ProcessInfo_t& info = m_Processes[hProcess]; + CloseHandle( info.m_hChildStderrWr ); + CloseHandle( info.m_hChildStdinRd ); + CloseHandle( info.m_hChildStdinWr ); + CloseHandle( info.m_hChildStdoutRd ); + CloseHandle( info.m_hChildStdoutWr ); + + m_Processes.Remove( hProcess ); +} + + +//----------------------------------------------------------------------------- +// Closes the process +//----------------------------------------------------------------------------- +void CProcessUtils::CloseProcess( ProcessHandle_t hProcess ) +{ + Assert( m_bInitialized ); + if ( hProcess != PROCESS_HANDLE_INVALID ) + { + WaitUntilProcessCompletes( hProcess ); + ShutdownProcess( hProcess ); + } +} + + +//----------------------------------------------------------------------------- +// Aborts the process +//----------------------------------------------------------------------------- +void CProcessUtils::AbortProcess( ProcessHandle_t hProcess ) +{ + Assert( m_bInitialized ); + if ( hProcess != PROCESS_HANDLE_INVALID ) + { + if ( !IsProcessComplete( hProcess ) ) + { + ProcessInfo_t& info = m_Processes[hProcess]; + TerminateProcess( info.m_hProcess, 1 ); + } + ShutdownProcess( hProcess ); + } +} + + +//----------------------------------------------------------------------------- +// Returns true if the process is complete +//----------------------------------------------------------------------------- +bool CProcessUtils::IsProcessComplete( ProcessHandle_t hProcess ) +{ + Assert( m_bInitialized ); + Assert( hProcess != PROCESS_HANDLE_INVALID ); + if ( m_hCurrentProcess != hProcess ) + return true; + + HANDLE h = m_Processes[hProcess].m_hProcess; + return ( WaitForSingleObject( h, 0 ) != WAIT_TIMEOUT ); +} + + +//----------------------------------------------------------------------------- +// Methods used to write input into a process +//----------------------------------------------------------------------------- +int CProcessUtils::SendProcessInput( ProcessHandle_t hProcess, char *pBuf, int nBufLen ) +{ + // Unimplemented yet + Assert( 0 ); + return 0; +} + + +//----------------------------------------------------------------------------- +// Methods used to read output back from a process +//----------------------------------------------------------------------------- +int CProcessUtils::GetActualProcessOutputSize( ProcessHandle_t hProcess ) +{ + Assert( hProcess != PROCESS_HANDLE_INVALID ); + + ProcessInfo_t& info = m_Processes[ hProcess ]; + if ( info.m_hChildStdoutRd == INVALID_HANDLE_VALUE ) + return 0; + + DWORD dwCount = 0; + if ( !PeekNamedPipe( info.m_hChildStdoutRd, NULL, NULL, NULL, &dwCount, NULL ) ) + { + char buf[ 512 ]; + Warning( "Could not read from pipe associated with command %s\n" + "Windows gave the error message:\n \"%s\"\n", + info.m_CommandLine.Get(), GetErrorString( buf, sizeof(buf) ) ); + return 0; + } + + // Add 1 for auto-NULL termination + return ( dwCount > 0 ) ? (int)dwCount + 1 : 0; +} + +int CProcessUtils::GetActualProcessOutput( ProcessHandle_t hProcess, char *pBuf, int nBufLen ) +{ + ProcessInfo_t& info = m_Processes[ hProcess ]; + if ( info.m_hChildStdoutRd == INVALID_HANDLE_VALUE ) + return 0; + + DWORD dwCount = 0; + DWORD dwRead = 0; + + // FIXME: Is there a way of making pipes be text mode so we don't get /n/rs back? + char *pTempBuf = (char*)_alloca( nBufLen ); + if ( !PeekNamedPipe( info.m_hChildStdoutRd, NULL, NULL, NULL, &dwCount, NULL ) ) + { + char buf[ 512 ]; + Warning( "Could not read from pipe associated with command %s\n" + "Windows gave the error message:\n \"%s\"\n", + info.m_CommandLine.Get(), GetErrorString( buf, sizeof(buf) ) ); + return 0; + } + + dwCount = min( dwCount, (DWORD)nBufLen - 1 ); + ReadFile( info.m_hChildStdoutRd, pTempBuf, dwCount, &dwRead, NULL); + + // Convert /n/r -> /n + int nActualCountRead = 0; + for ( unsigned int i = 0; i < dwRead; ++i ) + { + char c = pTempBuf[i]; + if ( c == '\r' ) + { + if ( ( i+1 < dwRead ) && ( pTempBuf[i+1] == '\n' ) ) + { + pBuf[nActualCountRead++] = '\n'; + ++i; + continue; + } + } + + pBuf[nActualCountRead++] = c; + } + + return nActualCountRead; +} + + +int CProcessUtils::GetProcessOutputSize( ProcessHandle_t hProcess ) +{ + Assert( m_bInitialized ); + if ( hProcess == PROCESS_HANDLE_INVALID ) + return 0; + + return GetActualProcessOutputSize( hProcess ) + m_Processes[hProcess].m_ProcessOutput.TellPut(); +} + + +int CProcessUtils::GetProcessOutput( ProcessHandle_t hProcess, char *pBuf, int nBufLen ) +{ + Assert( m_bInitialized ); + + if ( hProcess == PROCESS_HANDLE_INVALID ) + return 0; + + ProcessInfo_t &info = m_Processes[hProcess]; + int nCachedBytes = info.m_ProcessOutput.TellPut(); + int nBytesRead = 0; + if ( nCachedBytes ) + { + nBytesRead = min( nBufLen-1, nCachedBytes ); + info.m_ProcessOutput.Get( pBuf, nBytesRead ); + pBuf[ nBytesRead ] = 0; + nBufLen -= nBytesRead; + pBuf += nBytesRead; + if ( info.m_ProcessOutput.GetBytesRemaining() == 0 ) + { + info.m_ProcessOutput.Purge(); + } + + if ( nBufLen <= 1 ) + return nBytesRead; + } + + // Auto-NULL terminate + int nActualCountRead = GetActualProcessOutput( hProcess, pBuf, nBufLen ); + pBuf[nActualCountRead] = 0; + return nActualCountRead + nBytesRead + 1; +} + + +//----------------------------------------------------------------------------- +// Returns the exit code for the process. Doesn't work unless the process is complete +//----------------------------------------------------------------------------- +int CProcessUtils::GetProcessExitCode( ProcessHandle_t hProcess ) +{ + Assert( m_bInitialized ); + ProcessInfo_t &info = m_Processes[hProcess]; + DWORD nExitCode; + BOOL bOk = GetExitCodeProcess( info.m_hProcess, &nExitCode ); + if ( !bOk || nExitCode == STILL_ACTIVE ) + return -1; + return nExitCode; +} + + +//----------------------------------------------------------------------------- +// Waits until a process is complete +//----------------------------------------------------------------------------- +void CProcessUtils::WaitUntilProcessCompletes( ProcessHandle_t hProcess ) +{ + Assert( m_bInitialized ); + + // For the moment, we can only run one process at a time + if ( ( hProcess == PROCESS_HANDLE_INVALID ) || ( m_hCurrentProcess != hProcess ) ) + return; + + ProcessInfo_t &info = m_Processes[ hProcess ]; + + if ( info.m_hChildStdoutRd == INVALID_HANDLE_VALUE ) + { + WaitForSingleObject( info.m_hProcess, INFINITE ); + } + else + { + // NOTE: The called process can block during writes to stderr + stdout + // if the pipe buffer is empty. Therefore, waiting INFINITE is not + // possible here. We must queue up messages received to allow the + // process to continue + while ( WaitForSingleObject( info.m_hProcess, 100 ) == WAIT_TIMEOUT ) + { + int nLen = GetActualProcessOutputSize( hProcess ); + if ( nLen > 0 ) + { + int nPut = info.m_ProcessOutput.TellPut(); + info.m_ProcessOutput.EnsureCapacity( nPut + nLen ); + int nBytesRead = GetActualProcessOutput( hProcess, (char*)info.m_ProcessOutput.PeekPut(), nLen ); + info.m_ProcessOutput.SeekPut( CUtlBuffer::SEEK_HEAD, nPut + nBytesRead ); + } + } + } + + m_hCurrentProcess = PROCESS_HANDLE_INVALID; +} + + + diff --git a/vstdlib/random.cpp b/vstdlib/random.cpp new file mode 100644 index 0000000..e8a757a --- /dev/null +++ b/vstdlib/random.cpp @@ -0,0 +1,265 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Random number generator +// +// $Workfile: $ +// $NoKeywords: $ +//===========================================================================// + + +#include "vstdlib/random.h" +#include <math.h> +#include "dbg.h" + +#include "tier0/memdbgon.h" + +#define IA 16807 +#define IM 2147483647 +#define IQ 127773 +#define IR 2836 +#define NDIV (1+(IM-1)/NTAB) +#define MAX_RANDOM_RANGE 0x7FFFFFFFUL + +// fran1 -- return a random floating-point number on the interval [0,1) +// +#define AM (1.0/IM) +#define EPS 1.2e-7 +#define RNMX (1.0-EPS) + +//----------------------------------------------------------------------------- +// globals +//----------------------------------------------------------------------------- +static CUniformRandomStream s_UniformStream; +static CGaussianRandomStream s_GaussianStream; +static IUniformRandomStream *s_pUniformStream = &s_UniformStream; + + +//----------------------------------------------------------------------------- +// Installs a global random number generator, which will affect the Random functions above +//----------------------------------------------------------------------------- +void InstallUniformRandomStream( IUniformRandomStream *pStream ) +{ + s_pUniformStream = pStream ? pStream : &s_UniformStream; +} + + +//----------------------------------------------------------------------------- +// A couple of convenience functions to access the library's global uniform stream +//----------------------------------------------------------------------------- +void RandomSeed( int iSeed ) +{ + s_pUniformStream->SetSeed( iSeed ); +} + +float RandomFloat( float flMinVal, float flMaxVal ) +{ + return s_pUniformStream->RandomFloat( flMinVal, flMaxVal ); +} + +float RandomFloatExp( float flMinVal, float flMaxVal, float flExponent ) +{ + return s_pUniformStream->RandomFloatExp( flMinVal, flMaxVal, flExponent ); +} + +int RandomInt( int iMinVal, int iMaxVal ) +{ + return s_pUniformStream->RandomInt( iMinVal, iMaxVal ); +} + +float RandomGaussianFloat( float flMean, float flStdDev ) +{ + return s_GaussianStream.RandomFloat( flMean, flStdDev ); +} + + +//----------------------------------------------------------------------------- +// +// Implementation of the uniform random number stream +// +//----------------------------------------------------------------------------- +CUniformRandomStream::CUniformRandomStream() +{ + SetSeed(0); +} + +void CUniformRandomStream::SetSeed( int iSeed ) +{ + AUTO_LOCK( m_mutex ); + m_idum = ( ( iSeed < 0 ) ? iSeed : -iSeed ); + m_iy = 0; +} + +int CUniformRandomStream::GenerateRandomNumber() +{ + AUTO_LOCK( m_mutex ); + int j; + int k; + + if (m_idum <= 0 || !m_iy) + { + if (-(m_idum) < 1) + m_idum=1; + else + m_idum = -(m_idum); + + for ( j=NTAB+7; j>=0; j--) + { + k = (m_idum)/IQ; + m_idum = IA*(m_idum-k*IQ)-IR*k; + if (m_idum < 0) + m_idum += IM; + if (j < NTAB) + m_iv[j] = m_idum; + } + m_iy=m_iv[0]; + } + k=(m_idum)/IQ; + m_idum=IA*(m_idum-k*IQ)-IR*k; + if (m_idum < 0) + m_idum += IM; + j=m_iy/NDIV; + + // We're seeing some strange memory corruption in the contents of s_pUniformStream. + // Perhaps it's being caused by something writing past the end of this array? + // Bounds-check in release to see if that's the case. + if (j >= NTAB || j < 0) + { + DebuggerBreakIfDebugging(); + Warning("CUniformRandomStream had an array overrun: tried to write to element %d of 0..31. Contact Tom or Elan.\n", j); + // Ensure that NTAB is a power of two. + COMPILE_TIME_ASSERT( ( NTAB & ( NTAB - 1 ) ) == 0 ); + // Clamp j. + j &= NTAB - 1; + } + + m_iy=m_iv[j]; + m_iv[j] = m_idum; + + return m_iy; +} + +float CUniformRandomStream::RandomFloat( float flLow, float flHigh ) +{ + // float in [0,1) + float fl = AM * GenerateRandomNumber(); + if (fl > RNMX) + { + fl = RNMX; + } + return (fl * ( flHigh - flLow ) ) + flLow; // float in [low,high) +} + +float CUniformRandomStream::RandomFloatExp( float flMinVal, float flMaxVal, float flExponent ) +{ + // float in [0,1) + float fl = AM * GenerateRandomNumber(); + if (fl > RNMX) + { + fl = RNMX; + } + if ( flExponent != 1.0f ) + { + fl = powf( fl, flExponent ); + } + return (fl * ( flMaxVal - flMinVal ) ) + flMinVal; // float in [low,high) +} + +int CUniformRandomStream::RandomInt( int iLow, int iHigh ) +{ + //ASSERT(lLow <= lHigh); + unsigned int maxAcceptable; + unsigned int x = iHigh-iLow+1; + unsigned int n; + + // If you hit either of these assert, you're not getting back the random number that you thought you were. + Assert( x == iHigh-(int64)iLow+1 ); // Check that we didn't overflow int + Assert( x-1 <= MAX_RANDOM_RANGE ); // Check that the values provide an acceptable range + + if (x <= 1 || MAX_RANDOM_RANGE < x-1) + { + Assert( iLow == iHigh ); // This is the only time it is OK to have a range containing a single number + return iLow; + } + + // The following maps a uniform distribution on the interval [0,MAX_RANDOM_RANGE] + // to a smaller, client-specified range of [0,x-1] in a way that doesn't bias + // the uniform distribution unfavorably. Even for a worst case x, the loop is + // guaranteed to be taken no more than half the time, so for that worst case x, + // the average number of times through the loop is 2. For cases where x is + // much smaller than MAX_RANDOM_RANGE, the average number of times through the + // loop is very close to 1. + // + maxAcceptable = MAX_RANDOM_RANGE - ((MAX_RANDOM_RANGE+1) % x ); + do + { + n = GenerateRandomNumber(); + } while (n > maxAcceptable); + + return iLow + (n % x); +} + + +//----------------------------------------------------------------------------- +// +// Implementation of the gaussian random number stream +// We're gonna use the Box-Muller method (which actually generates 2 +// gaussian-distributed numbers at once) +// +//----------------------------------------------------------------------------- +CGaussianRandomStream::CGaussianRandomStream( IUniformRandomStream *pUniformStream ) +{ + AttachToStream( pUniformStream ); +} + + +//----------------------------------------------------------------------------- +// Attaches to a random uniform stream +//----------------------------------------------------------------------------- +void CGaussianRandomStream::AttachToStream( IUniformRandomStream *pUniformStream ) +{ + AUTO_LOCK( m_mutex ); + m_pUniformStream = pUniformStream; + m_bHaveValue = false; +} + + +//----------------------------------------------------------------------------- +// Generates random numbers +//----------------------------------------------------------------------------- +float CGaussianRandomStream::RandomFloat( float flMean, float flStdDev ) +{ + AUTO_LOCK( m_mutex ); + IUniformRandomStream *pUniformStream = m_pUniformStream ? m_pUniformStream : s_pUniformStream; + float fac,rsq,v1,v2; + + if (!m_bHaveValue) + { + // Pick 2 random #s from -1 to 1 + // Make sure they lie inside the unit circle. If they don't, try again + do + { + v1 = 2.0f * pUniformStream->RandomFloat() - 1.0f; + v2 = 2.0f * pUniformStream->RandomFloat() - 1.0f; + rsq = v1*v1 + v2*v2; + } while ((rsq > 1.0f) || (rsq == 0.0f)); + + // The box-muller transformation to get the two gaussian numbers + fac = sqrtf( -2.0f * log(rsq) / rsq ); + + // Store off one value for later use + m_flRandomValue = v1 * fac; + m_bHaveValue = true; + + return flStdDev * (v2 * fac) + flMean; + } + else + { + m_bHaveValue = false; + return flStdDev * m_flRandomValue + flMean; + } +} + + +//----------------------------------------------------------------------------- +// Creates a histogram (for testing) +//----------------------------------------------------------------------------- diff --git a/vstdlib/vcover.cpp b/vstdlib/vcover.cpp new file mode 100644 index 0000000..e4eb933 --- /dev/null +++ b/vstdlib/vcover.cpp @@ -0,0 +1,9 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "vstdlib/vcover.h" + +CVCoverage g_VCoverage; diff --git a/vstdlib/vstdlib.vpc b/vstdlib/vstdlib.vpc new file mode 100644 index 0000000..89c2214 --- /dev/null +++ b/vstdlib/vstdlib.vpc @@ -0,0 +1,157 @@ +//----------------------------------------------------------------------------- +// VSTDLIB.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR ".." +$Macro OUTBINDIR "$SRCDIR\..\game\bin" + +$include "$SRCDIR\vpc_scripts\source_dll_base.vpc" + +$MacroRequired "PLATSUBDIR" + +$Configuration +{ + + $General + { + // X360 version publishes to some other directory then copies here so we need to tell VPC to track this + // or else it won't know what depends on this project. + $AdditionalOutputFiles "$SRCDIR\lib\public\$(TargetName).lib" [$X360] + } + + $Compiler + { + $PreprocessorDefinitions "$BASE;VSTDLIB_DLL_EXPORT" + $GCC_ExtraCompilerFlags "-U_FORTIFY_SOURCE" [$LINUXALL] + } + + $Linker + { + $AdditionalDependencies "$BASE odbc32.lib odbccp32.lib" [$WINDOWS] + + // pc publishes the import library directly + $ImportLibrary "$LIBPUBLIC\$(TargetName).lib" [$WINDOWS] + + // 360 publishes the import library via a post build step + $ImportLibrary "$(TargetDir)\$(TargetName).lib" [$X360] + + + // 360 will auto generate a def file for this import library + $ModuleDefinitionFile " " [$X360] + $AdditionalOptions "$BASE /AUTODEF:xbox\xbox.def" [$X360] + + // Suppress this warning using the undocumented /ignore linker switch + // tier1.lib(KeyValues.obj) : warning LNK4217: locally defined symbol _KeyValuesSystem imported in function "public: static int __cdecl KeyValues::GetSymbolForStringClassic(char const *,bool)" (?GetSymbolForStringClassic@KeyValues@@SAHPBD_N@Z) + $AdditionalOptions "$BASE /ignore:4217" [$WINDOWS] + + $SystemLibraries "iconv" [$OSXALL] + $SystemFrameworks "CoreServices" [$OSXALL] + $GCC_ExtraLinkerFlags "-all_load" [$OSXALL] + + $ImportLibrary "$LIBPUBLIC\$_IMPLIB_PREFIX$OUTBINNAME$_IMPLIB_EXT" [$POSIX] + $OutputFile "$(OBJ_DIR)/$_IMPLIB_PREFIX$OUTBINNAME$_IMPLIB_EXT" [$POSIX] + } + + $PreLinkEvent [$WINDOWS] + { + $CommandLine "call $SRCDIR\vpc_scripts\valve_p4_edit.cmd $LIBPUBLIC\$(TargetName).lib $SRCDIR" "\n" \ + "$BASE" + } + + $PreLinkEvent [$X360] + { + // Run a pre-link event to clean the .def file from the last link + $CommandLine "if exist xbox\xbox.def del xbox\xbox.def" "\n" \ + "$BASE" + } + + $PostBuildEvent [$X360] + { + // Run a post build event to validate the .def file was correctly generated + $CommandLine "perl $SRCDIR\devtools\bin\make360def.pl -checkauto xbox\xbox.def" "\n" \ + "if exist $(TargetDir)$(TargetName).lib copy $(TargetDir)$(TargetName).lib $SRCDIR\lib\public\$(TargetName).lib" "\n" \ + "$BASE" + } + + $General [$POSIX] + { + $GameOutputFile "$OUTBINDIR/$_IMPLIB_DLL_PREFIX$OUTBINNAME$_DLL_EXT" + } +} + + + +$Project "vstdlib" +{ + $Folder "Source Files" + { + $File "xbox\___FirstModule.cpp" [$X360] + $File "GetStackPtr64.masm" [$WIN64] + { + $Configuration + { + $CustomBuildStep + { + // General + $CommandLine "$QUOTE$(VCInstallDir)bin\x86_amd64\ml64.exe$QUOTE /nologo /c /Fo$QUOTE$(IntDir)\$(InputName).obj$QUOTE $QUOTE$(InputPath)$QUOTE" + $Description "Compiling GetStackPtr64.masm" + $Outputs "$(IntDir)\$(InputName).obj" + } + } + } + $File "coroutine_win64.masm" [$WIN64] + { + $Configuration + { + $CustomBuildStep + { + // General + $CommandLine "$QUOTE$(VCInstallDir)bin\x86_amd64\ml64.exe$QUOTE /c /Fo$QUOTE$(IntDir)\$(InputName).obj$QUOTE $QUOTE$(InputPath)$QUOTE" + $Description "Compiling coroutine_win64.masm" + $Outputs "$(IntDir)\$(InputName).obj" + } + } + } + + $File "coroutine.cpp" [!$X360 && !$OSXALL] + { + $Configuration + { + $Compiler + { + $BasicRuntimeChecks "Default" + } + } + } + $File "cvar.cpp" + $File "jobthread.cpp" + $File "KeyValuesSystem.cpp" + $File "osversion.cpp" + $File "processutils.cpp" [$WINDOWS] + $File "random.cpp" + $File "vcover.cpp" + } + + $Folder "Public Header Files" + { + $File "$SRCDIR\public\vstdlib\cvar.h" + $File "$SRCDIR\public\vstdlib\coroutine.h" + $File "$SRCDIR\public\vstdlib\jobthread.h" + $File "$SRCDIR\public\vstdlib\IKeyValuesSystem.h" + $File "$SRCDIR\public\vstdlib\iprocessutils.h" + $File "$SRCDIR\public\tier1\mempool.h" + $File "$SRCDIR\public\vstdlib\osversion.h" + $File "$SRCDIR\public\vstdlib\random.h" + $File "$SRCDIR\public\vstdlib\vcover.h" + $File "$SRCDIR\public\vstdlib\vstdlib.h" + } + + $Folder "Link Libraries" + { + -$ImpLib vstdlib + $Lib "coroutine_osx" [$OSXALL] + } +} + diff --git a/vstdlib/vstdlib_exclude.vpc b/vstdlib/vstdlib_exclude.vpc new file mode 100644 index 0000000..f9f45e3 --- /dev/null +++ b/vstdlib/vstdlib_exclude.vpc @@ -0,0 +1,20 @@ +//----------------------------------------------------------------------------- +// vstdlib_exclude.vpc +// +// Project Script +//----------------------------------------------------------------------------- + +$MacroRequired "PLATSUBDIR" + +$Project +{ + $Folder "Link Libraries" + { + // Should match the sites that include this in base + -$Lib "$LIBPUBLIC\vstdlib" [$POSIX && !$IS_LIB_PROJECT] + -$Implib vstdlib [$POSIX] + -$File "$SRCDIR\lib\public\$_IMPLIB_PREFIXvstdlib$_IMPLIB_EXT" [$WIN32] + -$File "$SRCDIR\lib\public$PLATSUBDIR\$_IMPLIB_PREFIXvstdlib$_IMPLIB_EXT" [$WIN64] + -$File "$SRCDIR\lib\public\vstdlib_360.lib" [$X360] + } +} diff --git a/vstdlib/xbox/___FirstModule.cpp b/vstdlib/xbox/___FirstModule.cpp new file mode 100644 index 0000000..f37a6e9 --- /dev/null +++ b/vstdlib/xbox/___FirstModule.cpp @@ -0,0 +1,19 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// MUST BE THE FIRST MODULE IN THE LINK PROCESS TO ACHIEVE @1 +// +// This is a 360 specific trick to force this import library and the new 360 +// link option /AUTODEF to put CreateInterface at @1 (360 lacks named exports) and +// first in sequence. Otherwise, the valve interface techique that does a +// GetProcAddress( @1 ) gets the wrong function pointer. All other exported +// functions can appear in any order, but the oridnals should be autogened sequential. +//===========================================================================// + +#include "tier1/tier1.h" + +// Should be the first function that the linker 'sees' as an export +void* CreateInterfaceThunk( const char *pName, int *pReturnCode ) +{ + // descend into the real function + return CreateInterface( pName, pReturnCode ); +} |