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