summaryrefslogtreecommitdiff
path: root/vstdlib
diff options
context:
space:
mode:
Diffstat (limited to 'vstdlib')
-rw-r--r--vstdlib/KeyValuesSystem.cpp416
-rw-r--r--vstdlib/coroutine.cpp1157
-rw-r--r--vstdlib/coroutine_osx.vpc31
-rw-r--r--vstdlib/coroutine_win64.masm175
-rw-r--r--vstdlib/cvar.cpp899
-rw-r--r--vstdlib/getstackptr64.masm17
-rw-r--r--vstdlib/jobthread.cpp1457
-rw-r--r--vstdlib/osversion.cpp427
-rw-r--r--vstdlib/processutils.cpp473
-rw-r--r--vstdlib/random.cpp265
-rw-r--r--vstdlib/vcover.cpp9
-rw-r--r--vstdlib/vstdlib.vpc157
-rw-r--r--vstdlib/vstdlib_exclude.vpc20
-rw-r--r--vstdlib/xbox/___FirstModule.cpp19
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 );
+}