core/EventManager.cpp
author Nicholas Hastings <skamonkey@gmail.com>
Mon Feb 11 15:11:04 2013 -0500 (2013-02-11)
changeset 3786 cbbc2354ef1b
parent 3099 1b7bf8509625
child 3819 d17aed3d46bf
permissions -rw-r--r--
More, hopefully final CS:GO/protobuf usermessage fixups (bug 5588).
     1 /**
     2  * vim: set ts=4 sw=4 tw=99 noet :
     3  * =============================================================================
     4  * SourceMod
     5  * Copyright (C) 2004-2010 AlliedModders LLC.  All rights reserved.
     6  * =============================================================================
     7  *
     8  * This program is free software; you can redistribute it and/or modify it under
     9  * the terms of the GNU General Public License, version 3.0, as published by the
    10  * Free Software Foundation.
    11  * 
    12  * This program is distributed in the hope that it will be useful, but WITHOUT
    13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
    14  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
    15  * details.
    16  *
    17  * You should have received a copy of the GNU General Public License along with
    18  * this program.  If not, see <http://www.gnu.org/licenses/>.
    19  *
    20  * As a special exception, AlliedModders LLC gives you permission to link the
    21  * code of this program (as well as its derivative works) to "Half-Life 2," the
    22  * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
    23  * by the Valve Corporation.  You must obey the GNU General Public License in
    24  * all respects for all other code used.  Additionally, AlliedModders LLC grants
    25  * this exception to all derivative works.  AlliedModders LLC defines further
    26  * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
    27  * or <http://www.sourcemod.net/license.php>.
    28  *
    29  * Version: $Id$
    30  */
    31 
    32 #include "EventManager.h"
    33 #include "ForwardSys.h"
    34 #include "HandleSys.h"
    35 #include "PluginSys.h"
    36 #include "sm_stringutil.h"
    37 
    38 EventManager g_EventManager;
    39 
    40 SH_DECL_HOOK2(IGameEventManager2, FireEvent, SH_NOATTRIB, 0, bool, IGameEvent *, bool);
    41 
    42 const ParamType GAMEEVENT_PARAMS[] = {Param_Cell, Param_String, Param_Cell};
    43 typedef List<EventHook *> EventHookList;
    44 
    45 class EventForwardFilter : public IForwardFilter
    46 {
    47 	EventInfo *pEventInfo;
    48 public:
    49 	EventForwardFilter(EventInfo *pEventInfo) : pEventInfo(pEventInfo)
    50 	{
    51 	}
    52 
    53 	void Preprocess(IPluginFunction *fun, FwdParamInfo *params)
    54 	{
    55 		params[2].val = pEventInfo->bDontBroadcast ? 1 : 0;
    56 	}
    57 };
    58 
    59 EventManager::EventManager() : m_EventType(0)
    60 {
    61 	/* Create an event lookup trie */
    62 	m_EventHooks = sm_trie_create();
    63 }
    64 
    65 EventManager::~EventManager()
    66 {
    67 	sm_trie_destroy(m_EventHooks);
    68 
    69 	/* Free memory used by EventInfo structs if any */
    70 	CStack<EventInfo *>::iterator iter;
    71 	for (iter = m_FreeEvents.begin(); iter != m_FreeEvents.end(); iter++)
    72 	{
    73 		delete (*iter);
    74 	}
    75 
    76 	m_FreeEvents.popall();
    77 }
    78 
    79 void EventManager::OnSourceModAllInitialized()
    80 {
    81 	/* Add a hook for IGameEventManager2::FireEvent() */
    82 	SH_ADD_HOOK_MEMFUNC(IGameEventManager2, FireEvent, gameevents, this, &EventManager::OnFireEvent, false);
    83 	SH_ADD_HOOK_MEMFUNC(IGameEventManager2, FireEvent, gameevents, this, &EventManager::OnFireEvent_Post, true);
    84 
    85 	HandleAccess sec;
    86 
    87 	/* Handle access security for 'GameEvent' handle type */
    88 	sec.access[HandleAccess_Read] = 0;
    89 	sec.access[HandleAccess_Delete] = HANDLE_RESTRICT_IDENTITY | HANDLE_RESTRICT_OWNER;
    90 	sec.access[HandleAccess_Clone] = HANDLE_RESTRICT_IDENTITY | HANDLE_RESTRICT_OWNER;
    91 
    92 	/* Create the 'GameEvent' handle type */
    93 	m_EventType = g_HandleSys.CreateType("GameEvent", this, 0, NULL, &sec, g_pCoreIdent, NULL);
    94 }
    95 
    96 void EventManager::OnSourceModShutdown()
    97 {
    98 	/* Remove hook for IGameEventManager2::FireEvent() */
    99 	SH_REMOVE_HOOK_MEMFUNC(IGameEventManager2, FireEvent, gameevents, this, &EventManager::OnFireEvent, false);
   100 	SH_REMOVE_HOOK_MEMFUNC(IGameEventManager2, FireEvent, gameevents, this, &EventManager::OnFireEvent_Post, true);
   101 
   102 	/* Remove the 'GameEvent' handle type */
   103 	g_HandleSys.RemoveType(m_EventType, g_pCoreIdent);
   104 
   105 	/* Remove ourselves as listener for events */
   106 	gameevents->RemoveListener(this);
   107 }
   108 
   109 void EventManager::OnHandleDestroy(HandleType_t type, void *object)
   110 {
   111 	EventInfo *pInfo = static_cast<EventInfo *>(object);
   112 
   113 	/* Should only free event when created by a plugin */
   114 	if (pInfo->pOwner)
   115 	{
   116 		/* Free IGameEvent */
   117 		gameevents->FreeEvent(pInfo->pEvent);
   118 
   119 		/* Add EventInfo struct to free event stack */
   120 		m_FreeEvents.push(pInfo);
   121 	}
   122 }
   123 
   124 void EventManager::OnPluginUnloaded(IPlugin *plugin)
   125 {
   126 	EventHookList *pHookList;
   127 	EventHookList::iterator iter;
   128 	EventHook *pHook;
   129 
   130 	// If plugin has an event hook list...
   131 	if (plugin->GetProperty("EventHooks", reinterpret_cast<void **>(&pHookList), true))
   132 	{
   133 		for (iter = pHookList->begin(); iter != pHookList->end(); iter++)
   134 		{
   135 			pHook = (*iter);
   136 
   137 			if (--pHook->refCount == 0)
   138 			{
   139 				if (pHook->pPreHook)
   140 				{
   141 					g_Forwards.ReleaseForward(pHook->pPreHook);
   142 				}
   143 
   144 				if (pHook->pPostHook)
   145 				{
   146 					g_Forwards.ReleaseForward(pHook->pPostHook);
   147 				}
   148 
   149 				delete pHook;
   150 			}
   151 		}
   152 
   153 		delete pHookList;
   154 	}
   155 }
   156 
   157 /* IGameEventListener2::FireGameEvent */
   158 void EventManager::FireGameEvent(IGameEvent *pEvent)
   159 {
   160 	/* Not going to do anything here.
   161 	   Just need to add ourselves as a listener to make our hook on IGameEventManager2::FireEvent work */
   162 }
   163 
   164 #if SOURCE_ENGINE >= SE_LEFT4DEAD
   165 int EventManager::GetEventDebugID()
   166 {
   167 	return EVENT_DEBUG_ID_INIT;
   168 }
   169 #endif
   170 
   171 EventHookError EventManager::HookEvent(const char *name, IPluginFunction *pFunction, EventHookMode mode)
   172 {
   173 	EventHook *pHook;
   174 
   175 	/* If we aren't listening to this event... */
   176 	if (!gameevents->FindListener(this, name))
   177 	{
   178 		/* Then add ourselves */
   179 		if (!gameevents->AddListener(this, name, true))
   180 		{
   181 			/* If event doesn't exist... */
   182 			return EventHookErr_InvalidEvent;
   183 		}
   184 	}
   185 
   186 	/* If a hook structure does not exist... */
   187 	if (!sm_trie_retrieve(m_EventHooks, name, (void **)&pHook))
   188 	{
   189 		EventHookList *pHookList;
   190 		IPlugin *plugin = g_PluginSys.GetPluginByCtx(pFunction->GetParentContext()->GetContext());
   191 
   192 		/* Check plugin for an existing EventHook list */
   193 		if (!plugin->GetProperty("EventHooks", (void **)&pHookList))
   194 		{
   195 			pHookList = new EventHookList();
   196 			plugin->SetProperty("EventHooks", pHookList);
   197 		}
   198 
   199 		/* Create new GameEventHook structure */
   200 		pHook = new EventHook();
   201 
   202 		if (mode == EventHookMode_Pre)
   203 		{
   204 			/* Create forward for a pre hook */
   205 			pHook->pPreHook = g_Forwards.CreateForwardEx(NULL, ET_Hook, 3, GAMEEVENT_PARAMS);
   206 			/* Add to forward list */
   207 			pHook->pPreHook->AddFunction(pFunction);
   208 		} else {
   209 			/* Create forward for a post hook */
   210 			pHook->pPostHook = g_Forwards.CreateForwardEx(NULL, ET_Ignore, 3, GAMEEVENT_PARAMS);
   211 			/* Should we copy data from a pre hook to the post hook? */
   212 			pHook->postCopy = (mode == EventHookMode_Post);
   213 			/* Add to forward list */
   214 			pHook->pPostHook->AddFunction(pFunction);
   215 		}
   216 
   217 		/* Cache the name for post hooks */
   218 		pHook->name = sm_strdup(name);
   219 
   220 		/* Increase reference count */
   221 		pHook->refCount++;
   222 
   223 		/* Add hook structure to hook lists */
   224 		pHookList->push_back(pHook);
   225 		sm_trie_insert(m_EventHooks, name, pHook);
   226 
   227 		return EventHookErr_Okay;
   228 	}
   229 
   230 	/* Hook structure already exists at this point */
   231 
   232 	if (mode == EventHookMode_Pre)
   233 	{
   234 		/* Create pre hook forward if necessary */
   235 		if (!pHook->pPreHook)
   236 		{
   237 			pHook->pPreHook = g_Forwards.CreateForwardEx(NULL, ET_Event, 3, GAMEEVENT_PARAMS);
   238 		}
   239 
   240 		/* Add plugin function to forward list */
   241 		pHook->pPreHook->AddFunction(pFunction);
   242 	} else {
   243 		/* Create post hook forward if necessary */
   244 		if (!pHook->pPostHook)
   245 		{
   246 			pHook->pPostHook = g_Forwards.CreateForwardEx(NULL, ET_Ignore, 3, GAMEEVENT_PARAMS);
   247 		}
   248 
   249 		/* If postCopy is false, then we may want to set it to true */
   250 		if (!pHook->postCopy)
   251 		{
   252 			pHook->postCopy = (mode == EventHookMode_Post);
   253 		}
   254 
   255 		/* Add plugin function to forward list */
   256 		pHook->pPostHook->AddFunction(pFunction);
   257 	}
   258 
   259 	/* Increase reference count */
   260 	pHook->refCount++;
   261 
   262 	return EventHookErr_Okay;
   263 }
   264 
   265 EventHookError EventManager::UnhookEvent(const char *name, IPluginFunction *pFunction, EventHookMode mode)
   266 {
   267 	EventHook *pHook;
   268 	IChangeableForward **pEventForward;
   269 
   270 	/* If hook does not exist at all */
   271 	if (!sm_trie_retrieve(m_EventHooks, name, (void **)&pHook))
   272 	{
   273 		return EventHookErr_NotActive;
   274 	}
   275 
   276 	/* One forward to rule them all */
   277 	if (mode == EventHookMode_Pre)
   278 	{
   279 		pEventForward = &pHook->pPreHook;
   280 	} else {
   281 		pEventForward = &pHook->pPostHook;
   282 	}
   283 
   284 	/* Remove function from forward's list */
   285 	if (*pEventForward == NULL || !(*pEventForward)->RemoveFunction(pFunction))
   286 	{
   287 		return EventHookErr_InvalidCallback;
   288 	}
   289 
   290 	/* If forward's list contains 0 functions now, free it */
   291 	if ((*pEventForward)->GetFunctionCount() == 0)
   292 	{
   293 		g_Forwards.ReleaseForward(*pEventForward);
   294 		*pEventForward = NULL;
   295 	}
   296 
   297 	/* Decrement reference count */
   298 	if (--pHook->refCount == 0)
   299 	{
   300 		/* If reference count is now 0, free hook structure */
   301 
   302 		EventHookList *pHookList;
   303 		IPlugin *plugin = g_PluginSys.GetPluginByCtx(pFunction->GetParentContext()->GetContext());
   304 
   305 		/* Get plugin's event hook list */
   306 		if (!plugin->GetProperty("EventHooks", (void**)&pHookList))
   307 		{
   308 			return EventHookErr_NotActive;
   309 		}
   310 
   311 		/* Make sure the event was actually being hooked */
   312 		if (pHookList->find(pHook) == pHookList->end())
   313 		{
   314 			return EventHookErr_NotActive;
   315 		}
   316 
   317 		/* Remove current structure from plugin's list */
   318 		pHookList->remove(pHook);
   319 
   320 		/* Delete entry in trie */
   321 		sm_trie_delete(m_EventHooks, name);
   322 
   323 		/* Free the cached name */
   324 		delete pHook->name;
   325 
   326 		/* And finally free structure memory */
   327 		delete pHook;
   328 	}
   329 
   330 	return EventHookErr_Okay;
   331 }
   332 
   333 EventInfo *EventManager::CreateEvent(IPluginContext *pContext, const char *name, bool force)
   334 {
   335 	EventInfo *pInfo;
   336 	IGameEvent *pEvent = gameevents->CreateEvent(name, force);
   337 
   338 	if (pEvent)
   339 	{
   340 		if (m_FreeEvents.empty())
   341 		{
   342 			pInfo = new EventInfo();
   343 		} else {
   344 			pInfo = m_FreeEvents.front();
   345 			m_FreeEvents.pop();
   346 		}
   347 
   348 
   349 		pInfo->pEvent = pEvent;
   350 		pInfo->pOwner = pContext->GetIdentity();
   351 		pInfo->bDontBroadcast = false;
   352 
   353 		return pInfo;
   354 	}
   355 
   356 	return NULL;
   357 }
   358 
   359 void EventManager::FireEvent(EventInfo *pInfo, bool bDontBroadcast)
   360 {
   361 	/* Actually fire event now */
   362 	gameevents->FireEvent(pInfo->pEvent, bDontBroadcast);
   363 
   364 	/* IGameEvent is free at this point, so no one owns this */
   365 	pInfo->pOwner = NULL;
   366 
   367 	/* Add EventInfo struct to free event stack */
   368 	m_FreeEvents.push(pInfo);
   369 }
   370 
   371 void EventManager::CancelCreatedEvent(EventInfo *pInfo)
   372 {
   373 	/* Free event from IGameEventManager2 */
   374 	gameevents->FreeEvent(pInfo->pEvent);
   375 
   376 	/* IGameEvent is free at this point, so no one owns this */
   377 	pInfo->pOwner = NULL;
   378 
   379 	/* Add EventInfo struct to free event stack */
   380 	m_FreeEvents.push(pInfo);
   381 }
   382 
   383 /* IGameEventManager2::FireEvent hook */
   384 bool EventManager::OnFireEvent(IGameEvent *pEvent, bool bDontBroadcast)
   385 {
   386 	EventHook *pHook;
   387 	IChangeableForward *pForward;
   388 	const char *name;
   389 	cell_t res = Pl_Continue;
   390 	bool broadcast = bDontBroadcast;
   391 
   392 	/* The engine accepts NULL without crashing, so to prevent a crash in SM we ignore these */
   393 	if (!pEvent)
   394 	{
   395 		RETURN_META_VALUE(MRES_IGNORED, false);
   396 	}
   397 
   398 	name = pEvent->GetName();
   399 	
   400 	if (sm_trie_retrieve(m_EventHooks, name, reinterpret_cast<void **>(&pHook)))
   401 	{
   402 		/* Push the event onto the event stack.  The reference count is increased to make sure 
   403 		 * the structure is not garbage collected in between now and the post hook.
   404 		 */
   405 		pHook->refCount++;
   406 		m_EventStack.push(pHook);
   407 
   408 		pForward = pHook->pPreHook;
   409 
   410 		if (pForward)
   411 		{
   412 			EventInfo info(pEvent, NULL);
   413 			HandleSecurity sec(NULL, g_pCoreIdent);
   414 			Handle_t hndl = g_HandleSys.CreateHandle(m_EventType, &info, NULL, g_pCoreIdent, NULL);
   415 
   416 			info.bDontBroadcast = bDontBroadcast;
   417 
   418 			EventForwardFilter filter(&info);
   419 
   420 			pForward->PushCell(hndl);
   421 			pForward->PushString(name);
   422 			pForward->PushCell(bDontBroadcast);
   423 			pForward->Execute(&res, &filter);
   424 
   425 			broadcast = info.bDontBroadcast;
   426 
   427 			g_HandleSys.FreeHandle(hndl, &sec);
   428 		}
   429 
   430 		if (pHook->postCopy)
   431 		{
   432 			m_EventCopies.push(gameevents->DuplicateEvent(pEvent));
   433 		}
   434 
   435 		if (res)
   436 		{
   437 			gameevents->FreeEvent(pEvent);
   438 			RETURN_META_VALUE(MRES_SUPERCEDE, false);
   439 		}
   440 	}
   441 	else
   442 	{
   443 		m_EventStack.push(NULL);
   444 	}
   445 
   446 	if (broadcast != bDontBroadcast)
   447 		RETURN_META_VALUE_NEWPARAMS(MRES_IGNORED, true, &IGameEventManager2::FireEvent, (pEvent, broadcast));
   448 
   449 	RETURN_META_VALUE(MRES_IGNORED, true);
   450 }
   451 
   452 /* IGameEventManager2::FireEvent post hook */
   453 bool EventManager::OnFireEvent_Post(IGameEvent *pEvent, bool bDontBroadcast)
   454 {
   455 	EventHook *pHook;
   456 	EventInfo info;
   457 	IChangeableForward *pForward;
   458 	Handle_t hndl = 0;
   459 
   460 	/* The engine accepts NULL without crashing, so to prevent a crash in SM we ignore these */
   461 	if (!pEvent)
   462 	{
   463 		RETURN_META_VALUE(MRES_IGNORED, false);
   464 	}
   465 
   466 	pHook = m_EventStack.front();
   467 
   468 	if (pHook != NULL)
   469 	{
   470 		pForward = pHook->pPostHook;
   471 
   472 		if (pForward)
   473 		{
   474 			if (pHook->postCopy)
   475 			{
   476 				info.bDontBroadcast = bDontBroadcast;
   477 				info.pEvent = m_EventCopies.front();
   478 				info.pOwner = NULL;
   479 				hndl = g_HandleSys.CreateHandle(m_EventType, &info, NULL, g_pCoreIdent, NULL);
   480 
   481 				pForward->PushCell(hndl);
   482 			} else {
   483 				pForward->PushCell(BAD_HANDLE);
   484 			}
   485 
   486 			pForward->PushString(pHook->name);
   487 			pForward->PushCell(bDontBroadcast);
   488 			pForward->Execute(NULL);
   489 
   490 			if (pHook->postCopy)
   491 			{
   492 				/* Free handle */
   493 				HandleSecurity sec(NULL, g_pCoreIdent);
   494 				g_HandleSys.FreeHandle(hndl, &sec);
   495 
   496 				/* Free event structure */
   497 				gameevents->FreeEvent(info.pEvent);
   498 				m_EventCopies.pop();
   499 			}
   500 		}
   501 
   502 		/* Decrement reference count, check if a delayed delete is needed */
   503 		if (--pHook->refCount == 0)
   504 		{
   505 			assert(pHook->pPostHook == NULL);
   506 			assert(pHook->pPreHook == NULL);
   507 			sm_trie_delete(m_EventHooks, pHook->name);
   508 			delete pHook->name;
   509 			delete pHook;
   510 		}
   511 	}
   512 
   513 	m_EventStack.pop();
   514 
   515 	RETURN_META_VALUE(MRES_IGNORED, true);
   516 }