Tutorial *117*

Cvar Function Callback - By Robert 'Heffo' Heffernan (heffo@heffo.net, http://heffoquake.quakesrc.org)


Has there been a time during coding where you have wanted a way to have a function called every time a cvar has? I have, on a few occasions, infact a few commands have been dodged up in HeffoQuake to get around this, but I all of a sudden found it was easier to code in a Cvar Callback function than it was to do the hacks to get around the lack of one, so here it is, in all it's tutorial glory ;)


first of all, open up 'cvar.h' and change the cvar_s struct from this.....



typedef struct cvar_s

{

	char	*name;

	char	*string;

	qboolean archive;		// set to true to cause it to be saved to vars.rc

	qboolean server;		// notifies players when changed

	float	value;

	struct cvar_s *next;

} cvar_t;

to this...


typedef struct cvar_s

{

	char	*name;

	char	*string;

	qboolean archive;		// set to true to cause it to be saved to vars.rc

	qboolean server;		// notifies players when changed

	float	value;

	struct cvar_s *next;



	//Heffo - Cvar Callback Function

	void (*Cvar_Changed) (void);

	//Heffo - Cvar Callback Function



} cvar_t;

What this does is defines a pointer to a function in the cvar's structure which you can assign a function to later on. Now still in 'cvar.h' just below the cvar_s struct change this....


void 	Cvar_RegisterVariable (cvar_t *variable);

to this...


//Heffo - Cvar Callback Function

//void 	Cvar_RegisterVariable (cvar_t *variable);

void 	Cvar_RegisterVariable (cvar_t *variable, void *function);

//Heffo - Cvar Callback Function

this allows you to specify the cvar's callback function when you register the cvar with the cvar managment code. Open up 'cvar.c' and find the 'Cvar_Set' function and change it from this....


void Cvar_Set (char *var_name, char *value)

{

	cvar_t	*var;

	qboolean changed;



	var = Cvar_FindVar (var_name);

	if (!var)

	{	// there is an error in C code if this happens

		Con_Printf ("Cvar_Set: variable %s not found\n", var_name);

		return;

	}



	changed = Q_strcmp(var->string, value);



	Z_Free (var->string);	// free the old value string



	var->string = Z_Malloc (Q_strlen(value)+1);

	Q_strcpy (var->string, value);

	var->value = Q_atof (var->string);

	if (var->server && changed)

	{

		if (sv.active)

			SV_BroadcastPrintf ("\"%s\" changed to \"%s\"\n", var->name, var->string);

	}

}

to this..


void Cvar_Set (char *var_name, char *value)

{

	cvar_t	*var;

	qboolean changed;



	var = Cvar_FindVar (var_name);

	if (!var)

	{	// there is an error in C code if this happens

		Con_Printf ("Cvar_Set: variable %s not found\n", var_name);

		return;

	}



	changed = Q_strcmp(var->string, value);



	Z_Free (var->string);	// free the old value string



	var->string = Z_Malloc (Q_strlen(value)+1);

	Q_strcpy (var->string, value);

	var->value = Q_atof (var->string);

	if (var->server && changed)

	{

		if (sv.active)

			SV_BroadcastPrintf ("\"%s\" changed to \"%s\"\n", var->name, var->string);

	}



	//Heffo - Cvar Callback Function

	if(var->Cvar_Changed)

		var->Cvar_Changed();

	//Heffo - Cvar Callback Function

}

This checks to see if a callback function has assigned after the cvar has been set with it's new value, then calls the function. Scroll down 'cvar.c' some more until you find the 'Cvar_RegisterVariable' function and change it from this...


void Cvar_RegisterVariable (cvar_t *variable)

{

	char	*oldstr;



// first check to see if it has allready been defined

	if (Cvar_FindVar (variable->name))

	{

		Con_Printf ("Can't register variable %s, allready defined\n", variable->name);

		return;

	}



// check for overlap with a command

	if (Cmd_Exists (variable->name))

	{

		Con_Printf ("Cvar_RegisterVariable: %s is a command\n", variable->name);

		return;

	}



// copy the value off, because future sets will Z_Free it

	oldstr = variable->string;

	variable->string = Z_Malloc (Q_strlen(variable->string)+1);

	Q_strcpy (variable->string, oldstr);

	variable->value = Q_atof (variable->string);



// link the variable in

	variable->next = cvar_vars;

	cvar_vars = variable;

}

to this....


void Cvar_RegisterVariable (cvar_t *variable, void *function)

{

	char	*oldstr;



// first check to see if it has allready been defined

	if (Cvar_FindVar (variable->name))

	{

		Con_Printf ("Can't register variable %s, allready defined\n", variable->name);

		return;

	}



// check for overlap with a command

	if (Cmd_Exists (variable->name))

	{

		Con_Printf ("Cvar_RegisterVariable: %s is a command\n", variable->name);

		return;

	}



// copy the value off, because future sets will Z_Free it

	oldstr = variable->string;

	variable->string = Z_Malloc (Q_strlen(variable->string)+1);

	Q_strcpy (variable->string, oldstr);

	variable->value = Q_atof (variable->string);



// link the variable in

	variable->next = cvar_vars;

	cvar_vars = variable;



	//Heffo - Cvar Callback Function

	variable->Cvar_Changed = function;

	//Heffo - Cvar Callback Function

}

Now, this assigns the function you specify when you register the cvar to the cvar itself, fairly basic.
There is one final task you must do, otherwise you will end up with a hundred or so errors in your code. Everywhere there is a Cvar_RegisterVariable call, you must add a function to call, don't worry if you don't have a function you want called because you can specify NULL as a function, and the cvar will switch as normal, and nothing will be called.

Basically, search for lines like these.....


Cvar_RegisterVariable (&gl_cull);

Cvar_RegisterVariable (&gl_smoothmodels);

Cvar_RegisterVariable (&gl_affinemodels);

Cvar_RegisterVariable (&gl_polyblend);

Cvar_RegisterVariable (&gl_flashblend);

Cvar_RegisterVariable (&gl_playermip);

Cvar_RegisterVariable (&gl_nocolors);

and make them look like these...


Cvar_RegisterVariable (&gl_cull, NULL);

Cvar_RegisterVariable (&gl_smoothmodels, NULL);

Cvar_RegisterVariable (&gl_affinemodels, NULL);

Cvar_RegisterVariable (&gl_polyblend, NULL);

Cvar_RegisterVariable (&gl_flashblend, NULL);

Cvar_RegisterVariable (&gl_playermip, NULL);

Cvar_RegisterVariable (&gl_nocolors, NULL);

this sets a 'NULL' callback function.. For the fun part, as a quick test to see if the callback function works when you call a cvar do this... remember to remove these following changes once you are sure the callback function works correctly...
open 'host.c' and scroll down to the 'Host_InitLocal' function, and above it add...


//Heffo - Tempoary, Delete Me Later!

void Callback_Test (void)

{

	Con_Printf("Fraglimit set to %i\n", fraglimit.value);

}

//Heffo - Tempoary, Delete Me Later!

then go down into the 'Host_InitLocal' function and make this....


Cvar_RegisterVariable (&fraglimit, NULL);

look like this...


Cvar_RegisterVariable (&fraglimit, Callback_Test);

compile your code then start a game, pull the console down and type 'fraglimit 42' and you should have a line saying "Fraglimit set to 42" appear in the console, if you get this, everything works fine, and you can undo those last changes (unless you want to keep it), and then you can work on your own callback functions wherever you need them. I for example will be assigining a callback to my "sky" cvar so it loads the new skybox textures whenever the "sky" cvar is changed.
Keep in mind, the callback function will only be called if you use the 'Cvar_Set' and 'Cvar_SetValue' functions, so if you directly modify the values of the cvar a callback will not occur, also they will be called when the config file is loaded.


 
Not logged in
Sign up
Login:
Passwd: