core/EventManager.cpp
author Nicholas Hastings <skamonkey@gmail.com>
Thu Mar 11 17:43:46 2010 -0500 (2010-03-11)
changeset 2937 f5016767bfdf
parent 2883 1d7b415cef73
child 3099 1b7bf8509625
permissions -rw-r--r--
Added missing core voting cvars to sourcemod.cfg (bug 4224, r=pred)
     1 /**
     2  * vim: set ts=4 sw=4 tw=99 noet :
     3  * =============================================================================
     4  * SourceMod
     5  * Copyright (C) 2004-2008 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 EventHookError EventManager::HookEvent(const char *name, IPluginFunction *pFunction, EventHookMode mode)
   165 {
   166 	EventHook *pHook;
   167 
   168 	/* If we aren't listening to this event... */
   169 	if (!gameevents->FindListener(this, name))
   170 	{
   171 		/* Then add ourselves */
   172 		if (!gameevents->AddListener(this, name, true))
   173 		{
   174 			/* If event doesn't exist... */
   175 			return EventHookErr_InvalidEvent;
   176 		}
   177 	}
   178 
   179 	/* If a hook structure does not exist... */
   180 	if (!sm_trie_retrieve(m_EventHooks, name, (void **)&pHook))
   181 	{
   182 		EventHookList *pHookList;
   183 		IPlugin *plugin = g_PluginSys.GetPluginByCtx(pFunction->GetParentContext()->GetContext());
   184 
   185 		/* Check plugin for an existing EventHook list */
   186 		if (!plugin->GetProperty("EventHooks", (void **)&pHookList))
   187 		{
   188 			pHookList = new EventHookList();
   189 			plugin->SetProperty("EventHooks", pHookList);
   190 		}
   191 
   192 		/* Create new GameEventHook structure */
   193 		pHook = new EventHook();
   194 
   195 		if (mode == EventHookMode_Pre)
   196 		{
   197 			/* Create forward for a pre hook */
   198 			pHook->pPreHook = g_Forwards.CreateForwardEx(NULL, ET_Hook, 3, GAMEEVENT_PARAMS);
   199 			/* Add to forward list */
   200 			pHook->pPreHook->AddFunction(pFunction);
   201 		} else {
   202 			/* Create forward for a post hook */
   203 			pHook->pPostHook = g_Forwards.CreateForwardEx(NULL, ET_Ignore, 3, GAMEEVENT_PARAMS);
   204 			/* Should we copy data from a pre hook to the post hook? */
   205 			pHook->postCopy = (mode == EventHookMode_Post);
   206 			/* Add to forward list */
   207 			pHook->pPostHook->AddFunction(pFunction);
   208 		}
   209 
   210 		/* Cache the name for post hooks */
   211 		pHook->name = sm_strdup(name);
   212 
   213 		/* Increase reference count */
   214 		pHook->refCount++;
   215 
   216 		/* Add hook structure to hook lists */
   217 		pHookList->push_back(pHook);
   218 		sm_trie_insert(m_EventHooks, name, pHook);
   219 
   220 		return EventHookErr_Okay;
   221 	}
   222 
   223 	/* Hook structure already exists at this point */
   224 
   225 	if (mode == EventHookMode_Pre)
   226 	{
   227 		/* Create pre hook forward if necessary */
   228 		if (!pHook->pPreHook)
   229 		{
   230 			pHook->pPreHook = g_Forwards.CreateForwardEx(NULL, ET_Event, 3, GAMEEVENT_PARAMS);
   231 		}
   232 
   233 		/* Add plugin function to forward list */
   234 		pHook->pPreHook->AddFunction(pFunction);
   235 	} else {
   236 		/* Create post hook forward if necessary */
   237 		if (!pHook->pPostHook)
   238 		{
   239 			pHook->pPostHook = g_Forwards.CreateForwardEx(NULL, ET_Ignore, 3, GAMEEVENT_PARAMS);
   240 		}
   241 
   242 		/* If postCopy is false, then we may want to set it to true */
   243 		if (!pHook->postCopy)
   244 		{
   245 			pHook->postCopy = (mode == EventHookMode_Post);
   246 		}
   247 
   248 		/* Add plugin function to forward list */
   249 		pHook->pPostHook->AddFunction(pFunction);
   250 	}
   251 
   252 	/* Increase reference count */
   253 	pHook->refCount++;
   254 
   255 	return EventHookErr_Okay;
   256 }
   257 
   258 EventHookError EventManager::UnhookEvent(const char *name, IPluginFunction *pFunction, EventHookMode mode)
   259 {
   260 	EventHook *pHook;
   261 	IChangeableForward **pEventForward;
   262 
   263 	/* If hook does not exist at all */
   264 	if (!sm_trie_retrieve(m_EventHooks, name, (void **)&pHook))
   265 	{
   266 		return EventHookErr_NotActive;
   267 	}
   268 
   269 	/* One forward to rule them all */
   270 	if (mode == EventHookMode_Pre)
   271 	{
   272 		pEventForward = &pHook->pPreHook;
   273 	} else {
   274 		pEventForward = &pHook->pPostHook;
   275 	}
   276 
   277 	/* Remove function from forward's list */
   278 	if (*pEventForward == NULL || !(*pEventForward)->RemoveFunction(pFunction))
   279 	{
   280 		return EventHookErr_InvalidCallback;
   281 	}
   282 
   283 	/* If forward's list contains 0 functions now, free it */
   284 	if ((*pEventForward)->GetFunctionCount() == 0)
   285 	{
   286 		g_Forwards.ReleaseForward(*pEventForward);
   287 		*pEventForward = NULL;
   288 	}
   289 
   290 	/* Decrement reference count */
   291 	if (--pHook->refCount == 0)
   292 	{
   293 		/* If reference count is now 0, free hook structure */
   294 
   295 		EventHookList *pHookList;
   296 		IPlugin *plugin = g_PluginSys.GetPluginByCtx(pFunction->GetParentContext()->GetContext());
   297 
   298 		/* Get plugin's event hook list */
   299 		if (!plugin->GetProperty("EventHooks", (void**)&pHookList))
   300 		{
   301 			return EventHookErr_NotActive;
   302 		}
   303 
   304 		/* Make sure the event was actually being hooked */
   305 		if (pHookList->find(pHook) == pHookList->end())
   306 		{
   307 			return EventHookErr_NotActive;
   308 		}
   309 
   310 		/* Remove current structure from plugin's list */
   311 		pHookList->remove(pHook);
   312 
   313 		/* Delete entry in trie */
   314 		sm_trie_delete(m_EventHooks, name);
   315 
   316 		/* Free the cached name */
   317 		delete pHook->name;
   318 
   319 		/* And finally free structure memory */
   320 		delete pHook;
   321 	}
   322 
   323 	return EventHookErr_Okay;
   324 }
   325 
   326 EventInfo *EventManager::CreateEvent(IPluginContext *pContext, const char *name, bool force)
   327 {
   328 	EventInfo *pInfo;
   329 	IGameEvent *pEvent = gameevents->CreateEvent(name, force);
   330 
   331 	if (pEvent)
   332 	{
   333 		if (m_FreeEvents.empty())
   334 		{
   335 			pInfo = new EventInfo();
   336 		} else {
   337 			pInfo = m_FreeEvents.front();
   338 			m_FreeEvents.pop();
   339 		}
   340 
   341 
   342 		pInfo->pEvent = pEvent;
   343 		pInfo->pOwner = pContext->GetIdentity();
   344 		pInfo->bDontBroadcast = false;
   345 
   346 		return pInfo;
   347 	}
   348 
   349 	return NULL;
   350 }
   351 
   352 void EventManager::FireEvent(EventInfo *pInfo, bool bDontBroadcast)
   353 {
   354 	/* Actually fire event now */
   355 	gameevents->FireEvent(pInfo->pEvent, bDontBroadcast);
   356 
   357 	/* IGameEvent is free at this point, so no one owns this */
   358 	pInfo->pOwner = NULL;
   359 
   360 	/* Add EventInfo struct to free event stack */
   361 	m_FreeEvents.push(pInfo);
   362 }
   363 
   364 void EventManager::CancelCreatedEvent(EventInfo *pInfo)
   365 {
   366 	/* Free event from IGameEventManager2 */
   367 	gameevents->FreeEvent(pInfo->pEvent);
   368 
   369 	/* IGameEvent is free at this point, so no one owns this */
   370 	pInfo->pOwner = NULL;
   371 
   372 	/* Add EventInfo struct to free event stack */
   373 	m_FreeEvents.push(pInfo);
   374 }
   375 
   376 /* IGameEventManager2::FireEvent hook */
   377 bool EventManager::OnFireEvent(IGameEvent *pEvent, bool bDontBroadcast)
   378 {
   379 	EventHook *pHook;
   380 	IChangeableForward *pForward;
   381 	const char *name;
   382 	cell_t res = Pl_Continue;
   383 	bool broadcast = bDontBroadcast;
   384 
   385 	/* The engine accepts NULL without crashing, so to prevent a crash in SM we ignore these */
   386 	if (!pEvent)
   387 	{
   388 		RETURN_META_VALUE(MRES_IGNORED, false);
   389 	}
   390 
   391 	name = pEvent->GetName();
   392 	
   393 	if (sm_trie_retrieve(m_EventHooks, name, reinterpret_cast<void **>(&pHook)))
   394 	{
   395 		/* Push the event onto the event stack.  The reference count is increased to make sure 
   396 		 * the structure is not garbage collected in between now and the post hook.
   397 		 */
   398 		pHook->refCount++;
   399 		m_EventStack.push(pHook);
   400 
   401 		pForward = pHook->pPreHook;
   402 
   403 		if (pForward)
   404 		{
   405 			EventInfo info(pEvent, NULL);
   406 			HandleSecurity sec(NULL, g_pCoreIdent);
   407 			Handle_t hndl = g_HandleSys.CreateHandle(m_EventType, &info, NULL, g_pCoreIdent, NULL);
   408 
   409 			info.bDontBroadcast = bDontBroadcast;
   410 
   411 			EventForwardFilter filter(&info);
   412 
   413 			pForward->PushCell(hndl);
   414 			pForward->PushString(name);
   415 			pForward->PushCell(bDontBroadcast);
   416 			pForward->Execute(&res, &filter);
   417 
   418 			broadcast = info.bDontBroadcast;
   419 
   420 			g_HandleSys.FreeHandle(hndl, &sec);
   421 		}
   422 
   423 		if (pHook->postCopy)
   424 		{
   425 			m_EventCopies.push(gameevents->DuplicateEvent(pEvent));
   426 		}
   427 
   428 		if (res)
   429 		{
   430 			gameevents->FreeEvent(pEvent);
   431 			RETURN_META_VALUE(MRES_SUPERCEDE, false);
   432 		}
   433 	}
   434 	else
   435 	{
   436 		m_EventStack.push(NULL);
   437 	}
   438 
   439 	if (broadcast != bDontBroadcast)
   440 		RETURN_META_VALUE_NEWPARAMS(MRES_IGNORED, true, &IGameEventManager2::FireEvent, (pEvent, broadcast));
   441 
   442 	RETURN_META_VALUE(MRES_IGNORED, true);
   443 }
   444 
   445 /* IGameEventManager2::FireEvent post hook */
   446 bool EventManager::OnFireEvent_Post(IGameEvent *pEvent, bool bDontBroadcast)
   447 {
   448 	EventHook *pHook;
   449 	EventInfo info;
   450 	IChangeableForward *pForward;
   451 	Handle_t hndl = 0;
   452 
   453 	/* The engine accepts NULL without crashing, so to prevent a crash in SM we ignore these */
   454 	if (!pEvent)
   455 	{
   456 		RETURN_META_VALUE(MRES_IGNORED, false);
   457 	}
   458 
   459 	pHook = m_EventStack.front();
   460 
   461 	if (pHook != NULL)
   462 	{
   463 		pForward = pHook->pPostHook;
   464 
   465 		if (pForward)
   466 		{
   467 			if (pHook->postCopy)
   468 			{
   469 				info.bDontBroadcast = bDontBroadcast;
   470 				info.pEvent = m_EventCopies.front();
   471 				info.pOwner = NULL;
   472 				hndl = g_HandleSys.CreateHandle(m_EventType, &info, NULL, g_pCoreIdent, NULL);
   473 
   474 				pForward->PushCell(hndl);
   475 			} else {
   476 				pForward->PushCell(BAD_HANDLE);
   477 			}
   478 
   479 			pForward->PushString(pHook->name);
   480 			pForward->PushCell(bDontBroadcast);
   481 			pForward->Execute(NULL);
   482 
   483 			if (pHook->postCopy)
   484 			{
   485 				/* Free handle */
   486 				HandleSecurity sec(NULL, g_pCoreIdent);
   487 				g_HandleSys.FreeHandle(hndl, &sec);
   488 
   489 				/* Free event structure */
   490 				gameevents->FreeEvent(info.pEvent);
   491 				m_EventCopies.pop();
   492 			}
   493 		}
   494 
   495 		/* Decrement reference count, check if a delayed delete is needed */
   496 		if (--pHook->refCount == 0)
   497 		{
   498 			assert(pHook->pPostHook == NULL);
   499 			assert(pHook->pPreHook == NULL);
   500 			sm_trie_delete(m_EventHooks, pHook->name);
   501 			delete pHook->name;
   502 			delete pHook;
   503 		}
   504 	}
   505 
   506 	m_EventStack.pop();
   507 
   508 	RETURN_META_VALUE(MRES_IGNORED, true);
   509 }