You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1161 lines
37 KiB
1161 lines
37 KiB
//========= 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 |
|
#if defined(__arm__) |
|
#define GetStackPtr( pStackPtr ) byte *pStackPtr = (byte*)__builtin_frame_address(0) |
|
#else |
|
#define GetStackPtr( pStackPtr ) register byte *pStackPtr __asm__( "esp" ) |
|
#endif |
|
#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 |
|
}
|
|
|