core/CConVarManager.cpp
author David Anderson <dvander@alliedmods.net>
Fri Feb 16 00:04:14 2007 +0000 (2007-02-16)
changeset 499 961a75a6d466
parent 494 9b57583cc80b
child 511 c29f8796bcf4
permissions -rw-r--r--
added per-plugin command listing
     1 /**
     2 * ===============================================================
     3 * SourceMod (C)2004-2007 AlliedModders LLC.  All rights reserved.
     4 * ===============================================================
     5 *
     6 * This file is not open source and may not be copied without explicit
     7 * written permission of AlliedModders LLC.  This file may not be redistributed 
     8 * in whole or significant part.
     9 * For information, see LICENSE.txt or http://www.sourcemod.net/license.php
    10 *
    11 * Version: $Id$
    12 */
    13 
    14 #include "CConVarManager.h"
    15 #include "PluginSys.h"
    16 #include "ForwardSys.h"
    17 #include "HandleSys.h"
    18 #include "sm_srvcmds.h"
    19 #include "sm_stringutil.h"
    20 #include <sh_vector.h>
    21 
    22 CConVarManager g_ConVarManager;
    23 
    24 CConVarManager::CConVarManager() : m_ConVarType(0)
    25 {
    26 	// Create a convar lookup trie
    27 	m_ConVarCache = sm_trie_create();
    28 }
    29 
    30 CConVarManager::~CConVarManager()
    31 {
    32 	List<ConVarInfo *>::iterator i;
    33 
    34 	// Destroy our convar lookup trie
    35 	sm_trie_destroy(m_ConVarCache);
    36 
    37 	// Destroy all the ConVarInfo structures
    38 	for (i = m_ConVars.begin(); i != m_ConVars.end(); i++)
    39 	{
    40 		delete (*i);
    41 	}
    42 
    43 	m_ConVars.clear();
    44 }
    45 
    46 void CConVarManager::OnSourceModAllInitialized()
    47 {
    48 	HandleAccess sec;
    49 
    50 	// Set up access rights for the 'ConVar' handle type
    51 	sec.access[HandleAccess_Read] = 0;
    52 	sec.access[HandleAccess_Delete] = HANDLE_RESTRICT_IDENTITY | HANDLE_RESTRICT_OWNER;
    53 	sec.access[HandleAccess_Clone] = HANDLE_RESTRICT_IDENTITY | HANDLE_RESTRICT_OWNER;
    54 
    55 	// Create the 'ConVar' handle type
    56 	m_ConVarType = g_HandleSys.CreateType("ConVar", this, 0, NULL, &sec, g_pCoreIdent, NULL);
    57 
    58 	// Add the 'cvars' option to the 'sm' console command
    59 	g_RootMenu.AddRootConsoleCommand("convars", "View convars created by a plugin", this);
    60 }
    61 
    62 void CConVarManager::OnSourceModShutdown()
    63 {
    64 	IChangeableForward *fwd;
    65 	List<ConVarInfo *>::iterator i;
    66 
    67 	// Iterate list of ConVarInfo structures
    68 	for (i = m_ConVars.begin(); i != m_ConVars.end(); i++)
    69 	{
    70 		fwd = (*i)->changeForward;
    71 
    72 		// Free any convar-change forwards that still exist
    73 		if (fwd)
    74 		{
    75 			g_Forwards.ReleaseForward(fwd);
    76 		}
    77 	}
    78 
    79 	// Remove the 'cvars' option from the 'sm' console command
    80 	g_RootMenu.RemoveRootConsoleCommand("convars", this);
    81 
    82 	// Remove the 'ConVar' handle type
    83 	g_HandleSys.RemoveType(m_ConVarType, g_pCoreIdent);
    84 }
    85 
    86 void CConVarManager::OnPluginDestroyed(IPlugin *plugin)
    87 {
    88 	CVector<ConVar *> *cvarList;
    89 
    90 	// If plugin has a convar list, free its memory
    91 	if (plugin->GetProperty("ConVar", reinterpret_cast<void **>(&cvarList), true))
    92 	{
    93 		delete cvarList;
    94 	}
    95 }
    96 
    97 void CConVarManager::OnHandleDestroy(HandleType_t type, void *object)
    98 {
    99 	ConVarInfo *info;
   100 	ConCommandBase *cvar = static_cast<ConCommandBase *>(object);
   101 
   102 	// Find convar in lookup trie
   103 	sm_trie_retrieve(m_ConVarCache, cvar->GetName(), reinterpret_cast<void **>(&info));
   104 
   105 	// If convar was created by SourceMod plugin...
   106 	if (info->sourceMod)
   107 	{
   108 		// Then unregister it
   109 		g_SMAPI->UnregisterConCmdBase(g_PLAPI, cvar);
   110 	}
   111 }
   112 
   113 void CConVarManager::OnRootConsoleCommand(const char *command, unsigned int argcount)
   114 {
   115 	if (argcount >= 3)
   116 	{
   117 		int id = 1;
   118 
   119 		// Get plugin index that was passed
   120 		int num = atoi(g_RootMenu.GetArgument(2));
   121 		
   122 		// If invalid plugin index...
   123 		if (num < 1 || num > (int)g_PluginSys.GetPluginCount())
   124 		{
   125 			g_RootMenu.ConsolePrint("[SM] Plugin index %d not found.", num);
   126 			return;
   127 		}
   128 
   129 		// Get plugin object
   130 		CPlugin *pl = g_PluginSys.GetPluginByOrder(num);
   131 
   132 		CVector<ConVar *> *cvarList = NULL;
   133 
   134 		// Get convar list from 'ConVar' property
   135 		pl->GetProperty("ConVar", reinterpret_cast<void **>(&cvarList));
   136 
   137 		// If no cvar list...
   138 		if (cvarList == NULL)
   139 		{
   140 			g_RootMenu.ConsolePrint("[SM] No convars for \"%s\"", pl->GetPublicInfo()->name);
   141 			return;
   142 		}
   143 
   144 		g_RootMenu.ConsolePrint("[SM] Displaying convars for \"%s\"", pl->GetPublicInfo()->name);
   145 
   146 		// Iterate convar list and display each one
   147 		for (size_t i = 0; i < cvarList->size(); i++, id++)
   148 		{
   149 			ConVar *cvar = (*cvarList)[i];
   150 			g_RootMenu.ConsolePrint("  %02d \"%s\" = \"%s\"", id, cvar->GetName(), cvar->GetString()); 
   151 		}
   152 
   153 		return;
   154 	}
   155 
   156 	// Display usage of subcommand
   157 	g_RootMenu.ConsolePrint("[SM] Usage: sm convars <plugin #>");
   158 }
   159 
   160 Handle_t CConVarManager::CreateConVar(IPluginContext *pContext, const char *name, const char *defaultVal, const char *helpText, int flags, bool hasMin, float min, bool hasMax, float max)
   161 {
   162 	ConVar *cvar = NULL;
   163 	ConVarInfo *info = NULL;
   164 	CVector<ConVar *> *cvarList = NULL;
   165 	Handle_t hndl = 0;
   166 
   167 	// Find out if the convar exists already
   168 	cvar = icvar->FindVar(name);
   169 
   170 	// If the convar already exists...
   171 	if (cvar != NULL)
   172 	{
   173 		// First check if we already have a handle to it
   174 		if (sm_trie_retrieve(m_ConVarCache, name, reinterpret_cast<void **>(&info)))
   175 		{
   176 			// If we do, then return that handle
   177 			return info->handle;
   178 		} else {
   179 			// If we don't, then create a new handle from the convar
   180 			hndl = g_HandleSys.CreateHandle(m_ConVarType, cvar, NULL, g_pCoreIdent, NULL);
   181 
   182 			info = new ConVarInfo;
   183 			info->handle = hndl;
   184 			info->sourceMod = false;
   185 			info->changeForward = NULL;
   186 			info->origCallback = cvar->GetCallback();
   187 
   188 			m_ConVars.push_back(info);
   189 
   190 			return hndl;
   191 		}
   192 	}
   193 
   194 	// To prevent creating a convar that has the same name as a console command... ugh
   195 	ConCommandBase *pBase = icvar->GetCommands();
   196 
   197 	while (pBase)
   198 	{
   199 		if (strcmp(pBase->GetName(), name) == 0)
   200 		{
   201 			return pContext->ThrowNativeError("Convar \"%s\" was not created. A console command with the same name already exists.", name);
   202 		}
   203 
   204 		pBase = const_cast<ConCommandBase *>(pBase->GetNext());
   205 	}
   206 
   207 	// Since we didn't find an existing convar (or concmd with the same name), now we can finally create it!
   208 	cvar = new ConVar(name, defaultVal, flags, helpText, hasMin, min, hasMax, max);
   209 
   210 	// Find plugin creating convar
   211 	IPlugin *pl = g_PluginSys.FindPluginByContext(pContext->GetContext());
   212 
   213 	// Get convar list from 'ConVar' property of plugin
   214 	pl->GetProperty("ConVar", reinterpret_cast<void **>(&cvarList));
   215 
   216 	// If 'ConVar' property doesn't exist...
   217 	if (cvarList == NULL)
   218 	{
   219 		// Then create it
   220 		cvarList = new CVector<ConVar *>;
   221 		pl->SetProperty("ConVar", cvarList);
   222 	}
   223 
   224 	// Add new convar to plugin's list
   225 	cvarList->push_back(cvar);
   226 
   227 	// Create a handle from the new convar
   228 	hndl = g_HandleSys.CreateHandle(m_ConVarType, cvar, NULL, g_pCoreIdent, NULL);
   229 
   230 	info = new ConVarInfo;
   231 	info->handle = hndl;
   232 	info->sourceMod = true;
   233 	info->changeForward = NULL;
   234 	info->origCallback = NULL;
   235 
   236 	m_ConVars.push_back(info);
   237 
   238 	// Insert the handle into our lookup trie
   239 	sm_trie_insert(m_ConVarCache, name, info);
   240 
   241 	return hndl;
   242 }
   243 
   244 Handle_t CConVarManager::FindConVar(const char *name)
   245 {
   246 	ConVar *cvar = NULL;
   247 	ConVarInfo *info = NULL;
   248 	Handle_t hndl = 0;
   249 
   250 	// Search for convar
   251 	cvar = icvar->FindVar(name);
   252 
   253 	// If it doesn't exist, then return an invalid handle
   254 	if (cvar == NULL)
   255 	{
   256 		return BAD_HANDLE;
   257 	}
   258 
   259 	// At this point, convar exists, so find out if we already have a handle for it
   260 	if (sm_trie_retrieve(m_ConVarCache, name, reinterpret_cast<void **>(&info)))
   261 	{
   262 		// If we do, then return that handle
   263 		return info->handle;
   264 	}
   265 
   266 	// If we don't, then create a new handle from the convar
   267 	hndl = g_HandleSys.CreateHandle(m_ConVarType, cvar, NULL, g_pCoreIdent, NULL);
   268 
   269 	info = new ConVarInfo;
   270 	info->handle = hndl;
   271 	info->sourceMod = false;
   272 	info->changeForward = NULL;
   273 	info->origCallback = cvar->GetCallback();
   274 
   275 	m_ConVars.push_back(info);
   276 
   277 	// Insert the handle into our cache
   278 	sm_trie_insert(m_ConVarCache, name, info);
   279 
   280 	return hndl;
   281 }
   282 
   283 void CConVarManager::HookConVarChange(IPluginContext *pContext, ConVar *cvar, funcid_t funcid)
   284 {
   285 	IPluginFunction *func = pContext->GetFunctionById(funcid);
   286 	IChangeableForward *fwd = NULL;
   287 	char fwdName[64];
   288 	ConVarInfo *info = NULL;
   289 
   290 	// This shouldn't happen...
   291 	if (func == NULL)
   292 	{
   293 		pContext->ThrowNativeError("Invalid function specified");
   294 		return;
   295 	}
   296 
   297 	// Create a forward name
   298 	UTIL_Format(fwdName, sizeof(fwdName), "ConVar.%s", cvar->GetName());
   299 
   300 	// First find out if the forward already exists
   301 	g_Forwards.FindForward(fwdName, &fwd);
   302 
   303 	// If the forward doesn't exist...
   304 	if (fwd == NULL)
   305 	{
   306 		// This is the forward's parameter type list
   307 		ParamType p[] = {Param_Cell, Param_String, Param_String};
   308 
   309 		// Create the forward
   310 		fwd = g_Forwards.CreateForwardEx(fwdName, ET_Ignore, 3, p);
   311 
   312 		// Find the convar in the lookup trie
   313 		if (sm_trie_retrieve(m_ConVarCache, cvar->GetName(), reinterpret_cast<void **>(&info)))
   314 		{
   315 			// Set the convar's forward to the newly created one
   316 			info->changeForward = fwd;
   317 
   318 			// Set the convar's callback to our static one
   319 			cvar->InstallChangeCallback(OnConVarChanged);
   320 		}
   321 	}
   322 
   323 	// Add the function to the forward's list
   324 	fwd->AddFunction(func);
   325 }
   326 
   327 void CConVarManager::UnhookConVarChange(IPluginContext *pContext, ConVar *cvar, funcid_t funcid)
   328 {
   329 	IPluginFunction *func = pContext->GetFunctionById(funcid);
   330 	IChangeableForward *fwd = NULL;
   331 	ConVarInfo *info = NULL;
   332 
   333 	// This shouldn't happen...
   334 	if (func == NULL)
   335 	{
   336 		pContext->ThrowNativeError("Invalid function specified");
   337 		return;
   338 	}
   339 
   340 	// Find the convar in the lookup trie
   341 	if (sm_trie_retrieve(m_ConVarCache, cvar->GetName(), reinterpret_cast<void **>(&info)))
   342 	{
   343 		// Get the forward
   344 		fwd = info->changeForward;
   345 
   346 		// If the forward doesn't exist, we can't unhook anything
   347 		if (fwd == NULL)
   348 		{
   349 			pContext->ThrowNativeError("Convar \"%s\" has no active hook", cvar->GetName());
   350 			return;
   351 		}
   352 
   353 		// Remove the function from the forward's list
   354 		if (!fwd->RemoveFunction(func))
   355 		{
   356 			pContext->ThrowNativeError("Invalid hook callback specified for convar \"%s\"", cvar->GetName());
   357 			return;
   358 		}
   359 
   360 		// If the forward now has 0 functions in it...
   361 		if (fwd->GetFunctionCount() == 0)
   362 		{
   363 			// Free this forward
   364 			g_Forwards.ReleaseForward(fwd);
   365 			info->changeForward = NULL;
   366 
   367 			// Put the back the original convar callback
   368 			cvar->InstallChangeCallback(info->origCallback);
   369 		}
   370 	}
   371 }
   372 
   373 void CConVarManager::OnConVarChanged(ConVar *cvar, const char *oldValue)
   374 {
   375 	// If the values are the same...
   376 	if (strcmp(cvar->GetString(), oldValue) == 0)
   377 	{
   378 		// Exit early in order to not trigger callbacks
   379 		return;
   380 	}
   381 
   382 	Trie *cache = g_ConVarManager.GetConVarCache();
   383 	ConVarInfo *info;
   384 
   385 	// Find the convar in the lookup trie
   386 	sm_trie_retrieve(cache, cvar->GetName(), reinterpret_cast<void **>(&info));
   387 
   388 	FnChangeCallback origCallback = info->origCallback;
   389 	IChangeableForward *fwd = info->changeForward;
   390 
   391 	// If there was a change callback installed previously, call it
   392 	if (origCallback)
   393 	{
   394 		origCallback(cvar, oldValue);
   395 	}
   396 
   397 	// Now call forwards in plugins that have hooked this
   398 	fwd->PushCell(info->handle);
   399 	fwd->PushString(cvar->GetString());
   400 	fwd->PushString(oldValue);
   401 	fwd->Execute(NULL);
   402 }