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