|
|
//========== Copyright (c) Valve Corporation, All rights reserved. ==========//
//
// Purpose:
//
// $NoKeywords: $
//
//===========================================================================//
#include "vstdlib/ieventsystem.h"
#include "tier1/generichash.h"
#include "tier1/utllinkedlist.h"
#include "tier0/tslist.h"
#include "tier1/utltshash.h"
#include "tier1/tier1.h"
// NOTE: This has to be the last file included!
#include "tier0/memdbgon.h"
//-----------------------------------------------------------------------------
// A hash of the event name
//-----------------------------------------------------------------------------
typedef uint32 EventNameHash_t;
//-----------------------------------------------------------------------------
// An event queue is a thread-safe event queue
// NOTE: There are questions about whether we should have a secondary queue
// similar to vgui, where if you are doing event handling, you want to
// process the secondary queue prior to processing the next primary queue
// Problem is w/ threadsafety: what happens if other threads which are not
// on the thread processing the events posts an event?
//-----------------------------------------------------------------------------
class CEventQueue { public: CEventQueue(); ~CEventQueue();
// Posts an event
void PostEvent( CFunctorCallback *pCallback, CFunctorData *pData );
// Processes the event queue
void ProcessEvents( );
// Discards events that use a particular callback
void DiscardEvents( CFunctorCallback *pCallback );
// Refcount
int AddRef() { return ++m_nRefCount; } int Release() { return --m_nRefCount; } int RefCount() { return m_nRefCount; }
private: struct QueuedEvent_t { void *m_pTarget; CFunctorData *m_pData; CFunctorCallback *m_pCallback; };
void Cleanup();
CTSQueue< CFunctorCallback* > m_QueuedEventDiscards; CTSQueue< QueuedEvent_t > m_Queue; CInterlockedInt m_nRefCount;
#ifdef _DEBUG
bool m_bIsProcessingEvents; bool m_bIsDiscardingEvents; #endif
};
//-----------------------------------------------------------------------------
// Constructor, destructor
//-----------------------------------------------------------------------------
CEventQueue::CEventQueue() { #ifdef _DEBUG
m_bIsProcessingEvents = false; m_bIsDiscardingEvents = false; #endif
}
CEventQueue::~CEventQueue() { Assert( !m_bIsProcessingEvents ); Assert( !m_bIsDiscardingEvents ); Cleanup(); }
//-----------------------------------------------------------------------------
// Cleans up the event queue
//-----------------------------------------------------------------------------
void CEventQueue::Cleanup() { QueuedEvent_t event; while ( m_Queue.PopItem( &event ) ) { event.m_pCallback->Release(); event.m_pData->Release(); } }
//-----------------------------------------------------------------------------
// Posts an event
//-----------------------------------------------------------------------------
void CEventQueue::PostEvent( CFunctorCallback *pCallback, CFunctorData *pData ) { QueuedEvent_t event; event.m_pCallback = pCallback; event.m_pData = pData; pCallback->AddRef(); pData->AddRef(); m_Queue.PushItem( event ); }
//-----------------------------------------------------------------------------
// Discards events that use a particular callback
//-----------------------------------------------------------------------------
void CEventQueue::DiscardEvents( CFunctorCallback *pCallback ) { Assert( !m_bIsProcessingEvents );
#ifdef _DEBUG
m_bIsDiscardingEvents = true; #endif
m_QueuedEventDiscards.PushItem( pCallback );
#ifdef _DEBUG
m_bIsDiscardingEvents = false; #endif
}
//-----------------------------------------------------------------------------
// Processes the event queue
//-----------------------------------------------------------------------------
void CEventQueue::ProcessEvents( ) { Assert( !m_bIsProcessingEvents ); Assert( !m_bIsDiscardingEvents );
#ifdef _DEBUG
m_bIsProcessingEvents = true; #endif
if ( m_QueuedEventDiscards.Count() == 0 ) { // Dispatch all events
QueuedEvent_t event; while ( m_Queue.PopItem( &event ) ) { (*(event.m_pCallback))( event.m_pData ); event.m_pData->Release(); event.m_pCallback->Release(); } } else { // Build list of callbacks which are stale and need to die
CUtlVector< CFunctorCallback * > callbacks( 0, m_QueuedEventDiscards.Count() );
CFunctorCallback *pCallback = NULL; while( m_QueuedEventDiscards.PopItem( &pCallback ) ) { callbacks.AddToTail( pCallback ); }
// Only invoke events on non-stale callbacks
int nCount = callbacks.Count(); QueuedEvent_t event; while ( m_Queue.PopItem( &event ) ) { bool bFound = false; for ( int i = 0; i < nCount; ++i ) { bFound = ( event.m_pCallback == callbacks[i] ); if ( bFound ) break; }
if ( !bFound ) { (*(event.m_pCallback))( event.m_pData ); } event.m_pData->Release(); event.m_pCallback->Release(); } }
#ifdef _DEBUG
m_bIsProcessingEvents = false; #endif
}
//-----------------------------------------------------------------------------
// An event id maintains a list of all interested listeners
//-----------------------------------------------------------------------------
class CEventId { public: // Registers/unregisters a listener of this event
void RegisterListener( CEventQueue *pEventQueue, CFunctorCallback *pCallback ); void UnregisterListener( CEventQueue *pEventQueue, CFunctorCallback *pCallback );
// Unregisters all listeners associated with a particular queue
void UnregisterAllListeners( CEventQueue *pEventQueue );
// Posts an event
void PostEvent( CEventQueue *pEventQueue, const void *pListener, CFunctorData *pData );
private: struct SubscribedQueue_t { CEventQueue *m_pEventQueue; CFunctorCallback *m_pCallback; };
CUtlFixedLinkedList< SubscribedQueue_t > m_SubscribedQueueList; CThreadSpinRWLock m_ListenerLock; };
//-----------------------------------------------------------------------------
// Registers a listener of this event
//-----------------------------------------------------------------------------
void CEventId::RegisterListener( CEventQueue *pEventQueue, CFunctorCallback *pCallback ) { // NOTE: We could probably move the write locks so they cover less code
// but I'm wanting to be cautious
m_ListenerLock.LockForWrite();
intp i; for( i = m_SubscribedQueueList.Head(); i != m_SubscribedQueueList.InvalidIndex(); i = m_SubscribedQueueList.Next( i ) ) { if ( ( m_SubscribedQueueList[i].m_pEventQueue == pEventQueue ) && m_SubscribedQueueList[i].m_pCallback->IsEqual( pCallback ) ) { Warning( "Tried to install the same listener on the same event id + queue twice!\n" ); break; } }
if ( i == m_SubscribedQueueList.InvalidIndex() ) { SubscribedQueue_t queue; queue.m_pEventQueue = pEventQueue; queue.m_pCallback = pCallback; pCallback->AddRef(); pEventQueue->AddRef();
m_SubscribedQueueList.AddToTail( queue ); }
m_ListenerLock.UnlockWrite(); }
//-----------------------------------------------------------------------------
// Unregisters a listener
//-----------------------------------------------------------------------------
void CEventId::UnregisterListener( CEventQueue *pEventQueue, CFunctorCallback *pCallback ) { // NOTE: We could probably move the write locks so they cover less code
// but I'm wanting to be cautious
m_ListenerLock.LockForWrite();
intp i; for( i = m_SubscribedQueueList.Head(); i != m_SubscribedQueueList.InvalidIndex(); i = m_SubscribedQueueList.Next( i ) ) { if ( ( m_SubscribedQueueList[i].m_pEventQueue == pEventQueue ) && m_SubscribedQueueList[i].m_pCallback->IsEqual( pCallback ) ) break; }
if ( i != m_SubscribedQueueList.InvalidIndex() ) { CFunctorCallback *pCachedCallback = m_SubscribedQueueList[ i ].m_pCallback;
m_SubscribedQueueList.Remove( i );
// Remove the cached callback from the queued list of events
// if it has a non-zero refcount
if ( pCachedCallback->Release() > 0 ) { pEventQueue->DiscardEvents( pCachedCallback ); } pEventQueue->Release(); }
m_ListenerLock.UnlockWrite(); }
//-----------------------------------------------------------------------------
// Unregisters all listeners associated with a particular queue
//-----------------------------------------------------------------------------
void CEventId::UnregisterAllListeners( CEventQueue *pEventQueue ) { // NOTE: We could probably move the write locks so they cover less code
// but I'm wanting to be cautious
m_ListenerLock.LockForWrite();
intp j, next; for( j = m_SubscribedQueueList.Head(); j != m_SubscribedQueueList.InvalidIndex(); j = next ) { next = m_SubscribedQueueList.Next( j ); if ( m_SubscribedQueueList[j].m_pEventQueue != pEventQueue ) continue;
m_SubscribedQueueList[j].m_pCallback->Release(); pEventQueue->Release();
m_SubscribedQueueList.Remove( j ); }
m_ListenerLock.UnlockWrite(); }
//-----------------------------------------------------------------------------
// Posts an event
//-----------------------------------------------------------------------------
void CEventId::PostEvent( CEventQueue *pEventQueue, const void *pListener, CFunctorData *pData ) { m_ListenerLock.LockForRead();
for( intp i = m_SubscribedQueueList.Head(); i != m_SubscribedQueueList.InvalidIndex(); i = m_SubscribedQueueList.Next( i ) ) { SubscribedQueue_t &queue = m_SubscribedQueueList[i]; if ( pEventQueue && queue.m_pEventQueue != pEventQueue ) continue;
if ( pListener && pListener != queue.m_pCallback->GetTarget() ) continue;
queue.m_pEventQueue->PostEvent( queue.m_pCallback, pData ); }
m_ListenerLock.UnlockRead(); }
//-----------------------------------------------------------------------------
// Event system implementation
//-----------------------------------------------------------------------------
class CEventSystem : public CTier1AppSystem< IEventSystem > { typedef CTier1AppSystem< IEventSystem > BaseClass;
// Methods of IAppSystem
public:
// Methods of IEventSystem
public: virtual EventQueue_t CreateEventQueue(); virtual void DestroyEventQueue( EventQueue_t hQueue ); virtual void ProcessEvents( EventQueue_t hQueue ); virtual EventId_t RegisterEvent( const char *pEventName ); virtual void PostEventInternal( EventId_t nEventId, EventQueue_t hQueue, const void *pListener, CFunctorData *pData ); virtual void RegisterListener( EventId_t nEventId, EventQueue_t hQueue, CFunctorCallback *pCallback ); virtual void UnregisterListener( EventId_t nEventId, EventQueue_t hQueue, CFunctorCallback *pCallback );
// Other public methods
public: CEventSystem(); virtual ~CEventSystem();
private: CUtlTSHash< CEventId, 251, EventNameHash_t, CUtlTSHashGenericHash< 251, EventNameHash_t>, 8 > m_EventIds; };
//-----------------------------------------------------------------------------
// Globals
//-----------------------------------------------------------------------------
static CEventSystem s_EventSystem; EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CEventSystem, IEventSystem, EVENTSYSTEM_INTERFACE_VERSION, s_EventSystem )
//-----------------------------------------------------------------------------
// constructor, destructor
//-----------------------------------------------------------------------------
CEventSystem::CEventSystem() : m_EventIds( 256 ) { }
CEventSystem::~CEventSystem() { }
//-----------------------------------------------------------------------------
// Create, destroy an event queue
//-----------------------------------------------------------------------------
EventQueue_t CEventSystem::CreateEventQueue() { CEventQueue *pEventQueue = new CEventQueue; return (EventQueue_t)pEventQueue; }
void CEventSystem::DestroyEventQueue( EventQueue_t hQueue ) { CEventQueue *pEventQueue = ( CEventQueue* )( hQueue ); if ( !pEventQueue ) return;
if ( pEventQueue->RefCount() != 0 ) { // This means it's still sitting in a listener list
Warning( "Perf warning: Forgot to unregister listeners on event queue %p\n", hQueue );
int nCount = m_EventIds.Count(); UtlTSHashHandle_t *pHandles = (UtlTSHashHandle_t*)stackalloc( nCount * sizeof(UtlTSHashHandle_t) ); nCount = m_EventIds.GetElements( 0, nCount, pHandles );
for ( int i = 0; i < nCount; ++i ) { m_EventIds[ pHandles[i] ].UnregisterAllListeners( pEventQueue ); } }
delete pEventQueue; }
//-----------------------------------------------------------------------------
// Registers an event
//-----------------------------------------------------------------------------
EventId_t CEventSystem::RegisterEvent( const char *pEventName ) { EventNameHash_t hId = (EventNameHash_t)MurmurHash2( pEventName, Q_strlen(pEventName), 0xE1E47644 ); CDefaultTSHashConstructor< CEventId > constructor; UtlTSHashHandle_t hList = m_EventIds.Insert( hId, &constructor ); return ( EventId_t )( &m_EventIds[ hList ] ); }
//-----------------------------------------------------------------------------
// Registers, unregisters an event listener
//-----------------------------------------------------------------------------
void CEventSystem::RegisterListener( EventId_t nEventId, EventQueue_t hQueue, CFunctorCallback *pCallback ) { CEventQueue *pEventQueue = ( CEventQueue* )( hQueue ); if ( pEventQueue ) { CEventId *pEventId = ( CEventId* )nEventId; if ( pEventId ) { pEventId->RegisterListener( pEventQueue, pCallback ); } } pCallback->Release(); }
void CEventSystem::UnregisterListener( EventId_t nEventId, EventQueue_t hQueue, CFunctorCallback *pCallback ) { CEventQueue *pEventQueue = ( CEventQueue* )( hQueue ); if ( pEventQueue ) { CEventId *pEventId = ( CEventId* )nEventId; if ( pEventId ) { pEventId->UnregisterListener( pEventQueue, pCallback ); } } pCallback->Release(); }
//-----------------------------------------------------------------------------
// Posts an event to all current listeners in a single queue
//-----------------------------------------------------------------------------
void CEventSystem::PostEventInternal( EventId_t nEventId, EventQueue_t hQueue, const void *pListener, CFunctorData *pData ) { CEventQueue *pEventQueue = ( CEventQueue* )( hQueue ); CEventId *pEventId = (CEventId*)nEventId; if ( pEventId ) { pEventId->PostEvent( pEventQueue, pListener, pData ); } pData->Release(); }
//-----------------------------------------------------------------------------
// Process queued events on a single queue
//-----------------------------------------------------------------------------
void CEventSystem::ProcessEvents( EventQueue_t hQueue ) { CEventQueue *pEventQueue = ( CEventQueue* )( hQueue ); if ( pEventQueue ) { pEventQueue->ProcessEvents(); } }
|