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