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