Tracking CVAR Values

edited 2006 Feb 3 in Developers
<i>This post was originally made by <b>danij</b> on the dengDevs blog. It was posted under the categories: Engine, Games.</i>

Currently if a CVAR is used to control the state of a subsytem or perhaps an expensive value to calculate in an algorithm, or some other variable which cannot be changed directly (eg player name) - it is necessary to "track" the CVAR using local variables that store it's previously known value and compare them each tic/frame/whenever.

It would be very handy if we could declare simple callback functions for these kind of CVARs, called whenever the value of said CVAR is changed.

Comments

  • That kind of a callback would be a good addition, I think. It should be quite easy to add the callback pointer to cvar_t and call it whenever the value is changed in <tt>con_main.c</tt>. It could be something like:
    <pre>void (*notifyChanged)(cvar_t* cvar)</pre>

    One has to be careful with the cvar initialization arrays whose elements have not been properly enclosed in <tt>{ }</tt> braces, though. They will not tolerate a new member being added to the cvar_t struct.
  • <blockquote>One has to be careful with the cvar initialization arrays whose elements have not been properly enclosed in { } braces, though. They will not tolerate a new member being added to the cvar_t struct.</blockquote>
    Indeed. While I'm at it - is there anything else that would be a good addition to cvar_t?

    (default value perhaps?)
    float defaultVal; (for byte/int/float)
    const char* defaultString;
  • Currently the logic is that a cvar_t represents a global variable. The default value is the hardcoded value of that global variable. This has the benefit that the defaults are stored where the cvar is implemented.

    I can see how it would be helpful to add a command for reseting a cvar to its default value, but how to avoid having the default value in multiple places?

    Maybe the defaults could be saved automatically when the cvar is registered...
  • <blockquote>This has the benefit that the defaults are stored where the cvar is implemented.</blockquote>
    True.
    <blockquote>Maybe the defaults could be saved automatically when the cvar is registered...</blockquote>
    Thats probably a better idea.

    How about:
    <pre>int numCVarFloats;
    float *cvarFloatDefaults;
    int numCVarStrings;
    char **cvarStringDefaults;

    struct {
    /*...*/
    void *defptr; // pointer to default value
    } cvar_t;</pre>
    <tt>Con_AddVariable()</tt> Would be updated with the necessary book keeping.

    The default vars would be free'd at the same time as the CVars themselves (in <tt>Con_Shutdown()</tt>).
  • Since there isn't that many cvars, to simplify matters I would opt to store the defaults only as strings. That has the benefit that even large integers aren't affected by 32-bit floats' relative inaccuracy.

    There shouldn't be a need to add an extra pointer to cvar_t for this, though. The defaults could be stored into an array consisting of:
    <pre>typedef struct cvar_default_s {
    const char* name;
    const char* value;
    } cvar_default_t;</pre>

    Both the name and the value would point to strings specially allocated for this array. If the cvars array was never reallocated, we could just point to cvar_t's instead of the names, but reallocation will potentially have to move the entire array to a new location in memory, invalidating existing pointers.

    The cvarDefaults array would be completely internal to <tt>con_main.c</tt> and would be managed automatically by <tt>Con_AddVariable</tt> and a new command that restores the default value of a cvar.
  • <blockquote>There shouldn't be a need to add an extra pointer to cvar_t for this, though. The defaults could be stored into an array consisting of...</blockquote>
    I thought that approach might be preferable since:
    <ul>
    <li>cvars are sorted on odd occasion</li>
    <li>it would save having to store a duplicate of the cvar name along with the default value in order to search for the correct default cvar</li>
    </ul>
  • Let's look at it this way. cvar_t is a public struct that is visible to the games. It should only contain the bare minimum of data, and definitely nothing that the games cannot properly initialize.

    The default values are initialized once when the console is initialized. They are accessed extremely rarely and only on special request. This means the implementation can prioritize convenience over all other qualities.

    The default values should be handled in such a fashion that is invisible outside <tt>con_main.c</tt>. (This is actually related to the general principle of modularization and the reason why global variables are a bad thing. When it comes to C, a clean design is one where each .c file is an isolated module that has its own private data and a set of functions that other modules can call to operate on the data. This is analogous to the concept of data hiding in C++. Since the module is the only one who can operate on the data, the data's representation can be altered later on without breaking any of the other modules. You will note the similarities with what is being done with DMU and map data access from the engine vs. the games.)
  • <blockquote>Let's look at it this way. cvar_t is a public struct that is visible to the games. It should only contain the bare minimum of data, and definitely nothing that the games cannot properly initialize.</blockquote>
    Ah yes, I was forgetting that.

    The arguments you present for hiding the handling of the default values (no extra ptr in cvar_t), make perfect sense to me now that you prompted me to consider the implementation with the concepts of encapsulation, isolation and modularization in mind.

    Given the requirements mentioned the simplified solution you propose sounds more appropriate.
Sign In or Register to comment.