//========== Copyright © 2006, Valve Corporation, All rights reserved. ======== // // Purpose: // //============================================================================= #ifndef CALLQUEUE_H #define CALLQUEUE_H #include "tier0/tslist.h" #include "functors.h" #include "vstdlib/jobthread.h" #if defined( _WIN32 ) #pragma once #endif //----------------------------------------------------- // Avert thy eyes! Imagine rather: // // void QueueCall( , [args1, [arg2,]...] // void QueueCall( , , [args1, [arg2,]...] // void QueueRefCall( , <, [args1, [arg2,]...] //----------------------------------------------------- #define DEFINE_CALLQUEUE_NONMEMBER_QUEUE_CALL(N) \ template \ void QueueCall(FUNCTION_RETTYPE (*pfnProxied)( FUNC_BASE_TEMPLATE_FUNC_PARAMS_##N ) FUNC_ARG_FORMAL_PARAMS_##N ) \ { \ QueueFunctorInternal( CreateFunctor( pfnProxied FUNC_FUNCTOR_CALL_ARGS_##N ) ); \ } //------------------------------------- #define DEFINE_CALLQUEUE_MEMBER_QUEUE_CALL(N) \ template \ void QueueCall(OBJECT_TYPE_PTR pObject, FUNCTION_RETTYPE ( FUNCTION_CLASS::*pfnProxied )( FUNC_BASE_TEMPLATE_FUNC_PARAMS_##N ) FUNC_ARG_FORMAL_PARAMS_##N ) \ { \ QueueFunctorInternal( CreateFunctor( pObject, pfnProxied FUNC_FUNCTOR_CALL_ARGS_##N ) ); \ } //------------------------------------- #define DEFINE_CALLQUEUE_CONST_MEMBER_QUEUE_CALL(N) \ template \ void QueueCall(OBJECT_TYPE_PTR pObject, FUNCTION_RETTYPE ( FUNCTION_CLASS::*pfnProxied )( FUNC_BASE_TEMPLATE_FUNC_PARAMS_##N ) const FUNC_ARG_FORMAL_PARAMS_##N ) \ { \ QueueFunctorInternal( CreateFunctor( pObject, pfnProxied FUNC_FUNCTOR_CALL_ARGS_##N ) ); \ } //------------------------------------- #define DEFINE_CALLQUEUE_REF_COUNTING_MEMBER_QUEUE_CALL(N) \ template \ void QueueRefCall(OBJECT_TYPE_PTR pObject, FUNCTION_RETTYPE ( FUNCTION_CLASS::*pfnProxied )( FUNC_BASE_TEMPLATE_FUNC_PARAMS_##N ) FUNC_ARG_FORMAL_PARAMS_##N ) \ { \ QueueFunctorInternal( CreateRefCountingFunctor( pObject, pfnProxied FUNC_FUNCTOR_CALL_ARGS_##N ) ); \ } //------------------------------------- #define DEFINE_CALLQUEUE_REF_COUNTING_CONST_MEMBER_QUEUE_CALL(N) \ template \ void QueueRefCall(OBJECT_TYPE_PTR pObject, FUNCTION_RETTYPE ( FUNCTION_CLASS::*pfnProxied )( FUNC_BASE_TEMPLATE_FUNC_PARAMS_##N ) const FUNC_ARG_FORMAL_PARAMS_##N ) \ { \ QueueFunctorInternal( CreateRefCountingFunctor( pObject, pfnProxied FUNC_FUNCTOR_CALL_ARGS_##N ) ); \ \ } #define FUNC_GENERATE_QUEUE_METHODS() \ FUNC_GENERATE_ALL( DEFINE_CALLQUEUE_NONMEMBER_QUEUE_CALL ); \ FUNC_GENERATE_ALL( DEFINE_CALLQUEUE_MEMBER_QUEUE_CALL ); \ FUNC_GENERATE_ALL( DEFINE_CALLQUEUE_CONST_MEMBER_QUEUE_CALL );\ FUNC_GENERATE_ALL( DEFINE_CALLQUEUE_REF_COUNTING_MEMBER_QUEUE_CALL ); \ FUNC_GENERATE_ALL( DEFINE_CALLQUEUE_REF_COUNTING_CONST_MEMBER_QUEUE_CALL ) //----------------------------------------------------- template > class CCallQueueT { public: CCallQueueT() : m_bNoQueue( false ) { #ifdef _DEBUG m_nCurSerialNumber = 0; m_nBreakSerialNumber = (unsigned)-1; #endif } void DisableQueue( bool bDisable ) { if ( m_bNoQueue == bDisable ) { return; } if ( !m_bNoQueue ) CallQueued(); m_bNoQueue = bDisable; } bool IsDisabled() const { return m_bNoQueue; } int Count() { return m_queue.Count(); } void CallQueued() { if ( !m_queue.Count() ) { return; } m_queue.PushItem( NULL ); CFunctor *pFunctor = NULL; while ( m_queue.PopItem( &pFunctor ) && pFunctor != NULL ) { #ifdef _DEBUG if ( pFunctor->m_nUserID == m_nBreakSerialNumber) { m_nBreakSerialNumber = (unsigned)-1; } #endif (*pFunctor)(); pFunctor->Release(); } } void ParallelCallQueued( IThreadPool *pPool = NULL ) { if ( ! pPool ) { pPool = g_pThreadPool; } int nNumThreads = 1; if ( pPool ) { nNumThreads = MIN( pPool->NumThreads(), MAX( 1, Count() ) ); } if ( nNumThreads < 2 ) { CallQueued(); } else { int *pDummy = NULL; ParallelProcess( pPool, pDummy, nNumThreads, this, &CCallQueueT<>::ExecuteWrapper ); } } void QueueFunctor( CFunctor *pFunctor ) { Assert( pFunctor ); QueueFunctorInternal( RetAddRef( pFunctor ) ); } void Flush() { m_queue.PushItem( NULL ); CFunctor *pFunctor; while ( m_queue.PopItem( &pFunctor ) && pFunctor != NULL ) { pFunctor->Release(); } } FUNC_GENERATE_QUEUE_METHODS(); private: void ExecuteWrapper( int &nDummy ) // to match paralell process function template { CallQueued(); } void QueueFunctorInternal( CFunctor *pFunctor ) { if ( !m_bNoQueue ) { #ifdef _DEBUG pFunctor->m_nUserID = m_nCurSerialNumber++; #endif m_queue.PushItem( pFunctor ); } else { (*pFunctor)(); pFunctor->Release(); } } QUEUE_TYPE m_queue; bool m_bNoQueue; unsigned m_nCurSerialNumber; unsigned m_nBreakSerialNumber; }; class CCallQueue : public CCallQueueT<> { }; //----------------------------------------------------- // Optional interface that can be bound to concrete CCallQueue //----------------------------------------------------- class ICallQueue { public: void QueueFunctor( CFunctor *pFunctor ) { // [mhansen] If we grab an extra reference here then this functor never gets released. // That usually isn't too bad because the memory the functor is allocated in is cleared and // reused every frame. But, if the functor contains a CUtlDataEnvelope with more than // 4 bytes of data it allocates its own memory and, in that case, we need the destructor to // be called to free it. So, we don't want to grab the "extra" reference here. // The net result should be that after this call the pFunctor has a ref count of 1 (which // is held by the functor it gets nested in, which is stuck in the call queue) and after it // is executed the owning functor is destructed which causes it to release the reference // and this functor is then freed. This happens in imatersysteminternal.h:112 where the // destructor is called explictly for the owning functor: pFunctor->~CFunctor(); //QueueFunctorInternal( RetAddRef( pFunctor ) ); QueueFunctorInternal( pFunctor ); } FUNC_GENERATE_QUEUE_METHODS(); private: virtual void QueueFunctorInternal( CFunctor *pFunctor ) = 0; }; #endif // CALLQUEUE_H