#include "cbase.h" #include "cdll_client_int.h" #include "shared_classnames.h" #include "c_eventqueue.h" //----------------------------------------------------------------------------- // CEventQueue implementation // // Purpose: holds and executes a global prioritized queue of entity actions //----------------------------------------------------------------------------- DEFINE_FIXEDSIZE_ALLOCATOR( EventQueuePrioritizedEvent_t, 128, CUtlMemoryPool::GROW_SLOW ); CEventQueue g_EventQueue; CEventQueue::CEventQueue() { m_Events.m_flFireTime = 0.0f; m_Events.m_pNext = nullptr; Init(); } CEventQueue::~CEventQueue() { Clear(); } // Robin: Left here for backwards compatability. class CEventQueueSaveLoadProxy : public CBaseEntity { DECLARE_CLASS( CEventQueueSaveLoadProxy, CBaseEntity ); int Save( ISave &save ) { if ( !BaseClass::Save(save) ) return 0; // save out the message queue return g_EventQueue.Save( save ); } int Restore( IRestore &restore ) { if ( !BaseClass::Restore(restore) ) return 0; // restore the event queue int iReturn = g_EventQueue.Restore( restore ); #ifdef GAME_DLL // Now remove myself, because the CEventQueue_SaveRestoreBlockHandler // will handle future saves. UTIL_Remove( this ); #endif return iReturn; } }; LINK_ENTITY_TO_CLASS(event_queue_saveload_proxy, CEventQueueSaveLoadProxy); //----------------------------------------------------------------------------- // EVENT QUEUE SAVE / RESTORE //----------------------------------------------------------------------------- static short EVENTQUEUE_SAVE_RESTORE_VERSION = 1; class CEventQueue_SaveRestoreBlockHandler : public CDefSaveRestoreBlockHandler { public: const char *GetBlockName() { return "EventQueue"; } //--------------------------------- void Save( ISave *pSave ) { g_EventQueue.Save( *pSave ); } //--------------------------------- void WriteSaveHeaders( ISave *pSave ) { pSave->WriteShort( &EVENTQUEUE_SAVE_RESTORE_VERSION ); } //--------------------------------- void ReadRestoreHeaders( IRestore *pRestore ) { // No reason why any future version shouldn't try to retain backward compatability. The default here is to not do so. short version; pRestore->ReadShort( &version ); m_fDoLoad = ( version == EVENTQUEUE_SAVE_RESTORE_VERSION ); } //--------------------------------- void Restore( IRestore *pRestore, bool createPlayers ) { if ( m_fDoLoad ) { g_EventQueue.Restore( *pRestore ); } } private: bool m_fDoLoad; }; //----------------------------------------------------------------------------- CEventQueue_SaveRestoreBlockHandler g_EventQueue_SaveRestoreBlockHandler; //------------------------------------- ISaveRestoreBlockHandler *GetEventQueueSaveRestoreBlockHandler() { return &g_EventQueue_SaveRestoreBlockHandler; } void CEventQueue::Init( void ) { Clear(); } void CEventQueue::Clear( void ) { // delete all the events in the queue EventQueuePrioritizedEvent_t *pe = m_Events.m_pNext; while ( pe != NULL ) { EventQueuePrioritizedEvent_t *next = pe->m_pNext; delete pe; pe = next; } m_Events.m_pNext = NULL; } void CEventQueue::Dump( void ) { EventQueuePrioritizedEvent_t *pe = m_Events.m_pNext; Msg("Dumping event queue. Current time is: %.2f\n", gpGlobals->curtime ); while ( pe != NULL ) { EventQueuePrioritizedEvent_t *next = pe->m_pNext; Msg(" (%f) Target: '%s', Input: '%s', Parameter '%s'. Activator: '%s', Caller '%s'. \n", pe->m_flFireTime, STRING(pe->m_iTarget), STRING(pe->m_iTargetInput), pe->m_VariantValue.String(), pe->m_pActivator ? pe->m_pActivator->GetDebugName() : "None", pe->m_pCaller ? pe->m_pCaller->GetDebugName() : "None" ); pe = next; } Msg("Finished dump.\n"); } //----------------------------------------------------------------------------- // Purpose: adds the action into the correct spot in the priority queue, targeting entity via string name //----------------------------------------------------------------------------- void CEventQueue::AddEvent( const char *target, const char *targetInput, variant_t Value, float fireDelay, CBaseEntity *pActivator, CBaseEntity *pCaller, int outputID ) { // build the new event EventQueuePrioritizedEvent_t *newEvent = new EventQueuePrioritizedEvent_t; newEvent->m_flFireTime = gpGlobals->curtime + fireDelay * (1.0f / gpGlobals->frametime); // priority key in the priority queue newEvent->m_iTarget = MAKE_STRING( target ); newEvent->m_pEntTarget = NULL; newEvent->m_iTargetInput = MAKE_STRING( targetInput ); newEvent->m_pActivator = pActivator; newEvent->m_pCaller = pCaller; newEvent->m_VariantValue = Value; newEvent->m_iOutputID = outputID; AddEvent( newEvent ); } //----------------------------------------------------------------------------- // Purpose: adds the action into the correct spot in the priority queue, targeting entity via pointer //----------------------------------------------------------------------------- void CEventQueue::AddEvent( CBaseEntity *target, const char *targetInput, variant_t Value, float fireDelay, CBaseEntity *pActivator, CBaseEntity *pCaller, int outputID ) { // build the new event EventQueuePrioritizedEvent_t *newEvent = new EventQueuePrioritizedEvent_t; newEvent->m_flFireTime = gpGlobals->curtime + fireDelay * (1.0f / gpGlobals->frametime); // priority key in the priority queue newEvent->m_iTarget = NULL_STRING; newEvent->m_pEntTarget = target; newEvent->m_iTargetInput = MAKE_STRING( targetInput ); newEvent->m_pActivator = pActivator; newEvent->m_pCaller = pCaller; newEvent->m_VariantValue = Value; newEvent->m_iOutputID = outputID; AddEvent( newEvent ); } void CEventQueue::AddEvent( CBaseEntity *target, const char *action, float fireDelay, CBaseEntity *pActivator, CBaseEntity *pCaller, int outputID ) { variant_t Value; Value.Set( FIELD_VOID, NULL ); AddEvent( target, action, Value, fireDelay, pActivator, pCaller, outputID ); } //----------------------------------------------------------------------------- // Purpose: private function, adds an event into the list // Input : *newEvent - the (already built) event to add //----------------------------------------------------------------------------- void CEventQueue::AddEvent( EventQueuePrioritizedEvent_t *newEvent ) { // loop through the actions looking for a place to insert EventQueuePrioritizedEvent_t *pe; for ( pe = &m_Events; pe->m_pNext != NULL; pe = pe->m_pNext ) { if ( pe->m_pNext->m_flFireTime > newEvent->m_flFireTime ) { break; } } Assert( pe ); // insert newEvent->m_pNext = pe->m_pNext; newEvent->m_pPrev = pe; pe->m_pNext = newEvent; if ( newEvent->m_pNext ) { newEvent->m_pNext->m_pPrev = newEvent; } } void CEventQueue::RemoveEvent( EventQueuePrioritizedEvent_t *pe ) { Assert( pe->m_pPrev ); pe->m_pPrev->m_pNext = pe->m_pNext; if ( pe->m_pNext ) { pe->m_pNext->m_pPrev = pe->m_pPrev; } } //----------------------------------------------------------------------------- // Purpose: fires off any events in the queue who's fire time is (or before) the present time //----------------------------------------------------------------------------- void CEventQueue::ServiceEvents( void ) { EventQueuePrioritizedEvent_t *pe = m_Events.m_pNext; while ( pe != NULL && pe->m_flFireTime <= gpGlobals->curtime ) { MDLCACHE_CRITICAL_SECTION(); bool targetFound = false; // find the targets if ( pe->m_iTarget != NULL_STRING ) { // In the context the event, the searching entity is also the caller CBaseEntity *pSearchingEntity = pe->m_pCaller; CBaseEntity *target = NULL; while ( 1 ) { target = UTIL_FindEntityByName( target, pe->m_iTarget, pSearchingEntity, pe->m_pActivator, pe->m_pCaller ); if ( !target ) break; // pump the action into the target target->AcceptInput( STRING(pe->m_iTargetInput), pe->m_pActivator, pe->m_pCaller, pe->m_VariantValue, pe->m_iOutputID ); targetFound = true; } } // direct pointer if ( pe->m_pEntTarget != NULL ) { pe->m_pEntTarget->AcceptInput( STRING(pe->m_iTargetInput), pe->m_pActivator, pe->m_pCaller, pe->m_VariantValue, pe->m_iOutputID ); targetFound = true; } if ( !targetFound ) { // See if we can find a target if we treat the target as a classname if ( pe->m_iTarget != NULL_STRING ) { CBaseEntity *target = NULL; while ( 1 ) { target = UTIL_FindEntityByClassname( target, STRING(pe->m_iTarget) ); if ( !target ) break; // pump the action into the target target->AcceptInput( STRING(pe->m_iTargetInput), pe->m_pActivator, pe->m_pCaller, pe->m_VariantValue, pe->m_iOutputID ); targetFound = true; } } } if ( !targetFound ) { const char *pClass ="", *pName = ""; // might be NULL if ( pe->m_pCaller ) { pClass = STRING(pe->m_pCaller->m_iClassname); pName = pe->m_pCaller->GetDebugName(); } char szBuffer[256]; Q_snprintf( szBuffer, sizeof(szBuffer), "[Client] unhandled input: (%s) -> (%s), from (%s,%s); target entity not found\n", STRING(pe->m_iTargetInput), STRING(pe->m_iTarget), pClass, pName ); DevMsg( 2, "%s", szBuffer ); } // remove the event from the list (remembering that the queue may have been added to) RemoveEvent( pe ); delete pe; // restart the list (to catch any new items have probably been added to the queue) pe = m_Events.m_pNext; } } void CEventQueue::ServiceEvent( CBaseEntity* pActivator ) { EventQueuePrioritizedEvent_t *pe = m_Events.m_pNext; while ( pe != NULL && pe->m_flFireTime <= gpGlobals->curtime ) { if ( pe->m_pActivator != pActivator ) { pe = pe->m_pNext; continue; } MDLCACHE_CRITICAL_SECTION(); bool targetFound = false; // find the targets if ( pe->m_iTarget != NULL_STRING ) { // In the context the event, the searching entity is also the caller CBaseEntity *pSearchingEntity = pe->m_pCaller; CBaseEntity *target = NULL; while ( 1 ) { target = UTIL_FindEntityByName( target, pe->m_iTarget, pSearchingEntity, pe->m_pActivator, pe->m_pCaller ); if ( !target ) break; // pump the action into the target target->AcceptInput( STRING(pe->m_iTargetInput), pe->m_pActivator, pe->m_pCaller, pe->m_VariantValue, pe->m_iOutputID ); targetFound = true; } } // direct pointer if ( pe->m_pEntTarget != NULL ) { pe->m_pEntTarget->AcceptInput( STRING(pe->m_iTargetInput), pe->m_pActivator, pe->m_pCaller, pe->m_VariantValue, pe->m_iOutputID ); targetFound = true; } if ( !targetFound ) { // See if we can find a target if we treat the target as a classname if ( pe->m_iTarget != NULL_STRING ) { CBaseEntity *target = NULL; while ( 1 ) { target = UTIL_FindEntityByClassname( target, STRING(pe->m_iTarget) ); if ( !target ) break; // pump the action into the target target->AcceptInput( STRING(pe->m_iTargetInput), pe->m_pActivator, pe->m_pCaller, pe->m_VariantValue, pe->m_iOutputID ); targetFound = true; } } } if ( !targetFound ) { const char *pClass ="", *pName = ""; // might be NULL if ( pe->m_pCaller ) { pClass = STRING(pe->m_pCaller->m_iClassname); pName = pe->m_pCaller->GetDebugName(); } char szBuffer[256]; Q_snprintf( szBuffer, sizeof(szBuffer), "[Client] unhandled input: (%s) -> (%s), from (%s,%s); target entity not found\n", STRING(pe->m_iTargetInput), STRING(pe->m_iTarget), pClass, pName ); DevMsg( 2, "%s", szBuffer ); } // remove the event from the list (remembering that the queue may have been added to) RemoveEvent( pe ); delete pe; // restart the list (to catch any new items have probably been added to the queue) pe = m_Events.m_pNext; } } #ifdef GAME_DLL //----------------------------------------------------------------------------- // Purpose: Dumps the contents of the Entity I/O event queue to the console. //----------------------------------------------------------------------------- void CC_DumpEventQueue() { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; g_EventQueue.Dump(); } static ConCommand dumpeventqueue( "dumpeventqueue", CC_DumpEventQueue, "Dump the contents of the Entity I/O event queue to the console." ); #endif //----------------------------------------------------------------------------- // Purpose: Removes all pending events from the I/O queue that were added by the // given caller. // // TODO: This is only as reliable as callers are in passing the correct // caller pointer when they fire the outputs. Make more foolproof. //----------------------------------------------------------------------------- void CEventQueue::CancelEvents( CBaseEntity *pCaller ) { if (!pCaller) return; EventQueuePrioritizedEvent_t *pCur = m_Events.m_pNext; while (pCur != NULL) { bool bDelete = false; if (pCur->m_pCaller == pCaller) { // Pointers match; make sure everything else matches. if (!stricmp(pCur->m_pCaller->GetDebugName(), pCaller->GetDebugName()) && !stricmp(pCur->m_pCaller->GetClassname(), pCaller->GetClassname())) { // Found a matching event; delete it from the queue. bDelete = true; } } EventQueuePrioritizedEvent_t *pCurSave = pCur; pCur = pCur->m_pNext; if (bDelete) { RemoveEvent( pCurSave ); delete pCurSave; } } } //----------------------------------------------------------------------------- // Purpose: Removes all pending events of the specified type from the I/O queue of the specified target // // TODO: This is only as reliable as callers are in passing the correct // caller pointer when they fire the outputs. Make more foolproof. //----------------------------------------------------------------------------- void CEventQueue::CancelEventOn( CBaseEntity *pTarget, const char *sInputName ) { if (!pTarget) return; EventQueuePrioritizedEvent_t *pCur = m_Events.m_pNext; const size_t inputNameSize = strlen(sInputName); while (pCur != nullptr) { bool bDelete = false; if (pCur->m_pEntTarget == pTarget) { if (!Q_strncmp(STRING(pCur->m_iTargetInput), sInputName, inputNameSize)) { // Found a matching event; delete it from the queue. bDelete = true; } } EventQueuePrioritizedEvent_t *pCurSave = pCur; pCur = pCur->m_pNext; if (bDelete) { RemoveEvent( pCurSave ); delete pCurSave; } } } //----------------------------------------------------------------------------- // Purpose: Return true if the target has any pending inputs. // Input : *pTarget - // *sInputName - NULL for any input, or a specified one //----------------------------------------------------------------------------- bool CEventQueue::HasEventPending( CBaseEntity *pTarget, const char *sInputName ) { if (!pTarget) return false; EventQueuePrioritizedEvent_t *pCur = m_Events.m_pNext; const size_t inputNameSize = strlen(sInputName); while (pCur != nullptr) { if (pCur->m_pEntTarget == pTarget) { if (!sInputName || !Q_strncmp(STRING(pCur->m_iTargetInput), sInputName, inputNameSize)) return true; } pCur = pCur->m_pNext; } return false; } void ServiceEventQueue( CBaseEntity* pActivator ) { VPROF("ServiceEventQueue()"); if ( pActivator ) { g_EventQueue.ServiceEvent( pActivator ); } else { g_EventQueue.ServiceEvents(); } } // save data description for the event queue BEGIN_SIMPLE_DATADESC( CEventQueue ) // These are saved explicitly in CEventQueue::Save below // DEFINE_FIELD( m_Events, EventQueuePrioritizedEvent_t ), DEFINE_FIELD( m_iListCount, FIELD_INTEGER ), // this value is only used during save/restore END_DATADESC() // save data for a single event in the queue BEGIN_SIMPLE_DATADESC( EventQueuePrioritizedEvent_t ) DEFINE_FIELD( m_flFireTime, FIELD_FLOAT ), DEFINE_FIELD( m_iTarget, FIELD_STRING ), DEFINE_FIELD( m_iTargetInput, FIELD_STRING ), DEFINE_FIELD( m_pActivator, FIELD_EHANDLE ), DEFINE_FIELD( m_pCaller, FIELD_EHANDLE ), DEFINE_FIELD( m_pEntTarget, FIELD_EHANDLE ), DEFINE_FIELD( m_iOutputID, FIELD_INTEGER ), DEFINE_CUSTOM_FIELD( m_VariantValue, variantFuncs ), // DEFINE_FIELD( m_pNext, FIELD_??? ), // DEFINE_FIELD( m_pPrev, FIELD_??? ), END_DATADESC() int CEventQueue::Save( ISave &save ) { // count the number of items in the queue EventQueuePrioritizedEvent_t *pe; m_iListCount = 0; for ( pe = m_Events.m_pNext; pe != NULL; pe = pe->m_pNext ) { m_iListCount++; } // save that value out to disk, so we know how many to restore if ( !save.WriteFields( "EventQueue", this, NULL, m_DataMap.dataDesc, m_DataMap.dataNumFields ) ) return 0; // cycle through all the events, saving them all for ( pe = m_Events.m_pNext; pe != NULL; pe = pe->m_pNext ) { if ( !save.WriteFields( "PEvent", pe, NULL, pe->m_DataMap.dataDesc, pe->m_DataMap.dataNumFields ) ) return 0; } return 1; } int CEventQueue::Restore( IRestore &restore ) { // clear the event queue Clear(); // rebuild the event queue by restoring all the queue items EventQueuePrioritizedEvent_t tmpEvent; // load the number of items saved if ( !restore.ReadFields( "EventQueue", this, NULL, m_DataMap.dataDesc, m_DataMap.dataNumFields ) ) return 0; for ( int i = 0; i < m_iListCount; i++ ) { if ( !restore.ReadFields( "PEvent", &tmpEvent, NULL, tmpEvent.m_DataMap.dataDesc, tmpEvent.m_DataMap.dataNumFields ) ) return 0; // add the restored event into the list if ( tmpEvent.m_pEntTarget ) { AddEvent( tmpEvent.m_pEntTarget, STRING(tmpEvent.m_iTargetInput), tmpEvent.m_VariantValue, #ifdef TF_DLL tmpEvent.m_flFireTime - engine->GetServerTime(), #else tmpEvent.m_flFireTime - gpGlobals->curtime, #endif tmpEvent.m_pActivator, tmpEvent.m_pCaller, tmpEvent.m_iOutputID ); } else { AddEvent( STRING(tmpEvent.m_iTarget), STRING(tmpEvent.m_iTargetInput), tmpEvent.m_VariantValue, #ifdef TF_DLL tmpEvent.m_flFireTime - engine->GetServerTime(), #else tmpEvent.m_flFireTime - gpGlobals->curtime, #endif tmpEvent.m_pActivator, tmpEvent.m_pCaller, tmpEvent.m_iOutputID ); } } return 1; }