Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

Many famous game such as Quake or Half-Life use CVars to allow easy and flexible game parameter manipulations.

You can simply open up the console and write something like

sv_cheats 1

to enable cheat codes.

CVars are used intensively during the game - if they were set or retrieved by string name lookup there would certainly be a performance hit. Therefore, I'm assuming CVars are somehow cached.

How would this kind of system be implemented? I thought about storing pointers to the real values in a map having the CVar names, and then use the real values in the game. Is this a viable approach? Do the AAA games have a better one?

Example:

struct Game 
{
    std::map<std::string, int*> cVars;
    int volume, sensitivity; // Example game parameters

    Game() 
    {
        cVars.emplace("volume", &volume);
        cVars.emplace("sensitivity", &sensitivity);
    }

    void setCVar(std::string mName, int mValue) 
    {
        *cVars[mName] = mValue;
    }
};
share|improve this question
3  
"there would certainly be a performance hit" => I would not try to optimize until you actually experience the performance hit. –  Matthias Jul 23 at 15:25
    
See my reply to this question. At the end I also provide a link to the source code of Doom3, so that you can take a look at a CVar system that actually went into production. –  glampert Jul 23 at 17:46

3 Answers 3

up vote 4 down vote accepted

First, avoid premature optimization. As Matthias notes in the comments, keep your implementation simple until you actually experience a performance problem that you can attribute (via profiling) to the configuration variable system.

One of the big potential performance issues in an implementation like the one you have described will be the string comparisons, so you will potentially see a gain by removing them. A common way to remove string comparisons is to hash the strings to integers, which are generally faster to compare.

If all your variable keys are known up-front, you can do this hashing at compile time or load time (whenever the earliest you know the full set of keys is). Then you only need to hash the user input, and all subsequent comparisons to look up in the map are done via integers. In fact, if you do know everything up front you can potentially "hash" every variable to an ordered list of integers and simply use an array for lookup. If you don't know everything up-front, you can still leverage hashing, but the lack of foreknowledge means you lose out on the opportunity to achieve a perfect mapping.

Alternatively, you can store the variable data in a trie which is a tree-like structure with (in this case) up to 26 children per node; paths through the tree spell out the variable keys and the leaf nodes store the actual variable data.

In both cases you will want to build yourself an API that wraps storage of and access to those variables -- do not store a std::map directly in your Game object, as you did in your example code, because that will make it more difficult for you to tweak or replace the underlying storage and retrieval mechanism if/when you discover there are performance issues.

share|improve this answer

You are talking about string interning. It is widely used in compilers to represent identifiers. Basically, when the string is parsed from the command line or a text file, it is looked up in a global table. If it is already present, then it is assigned a number, and that number is returned. If not, then it is assigned the next available number, which is incremented.

An interned string is therefore just an integer. However, an interned string is meaningless without the global table to tell what actual string it is. This allows maps from strings to other types to just be integer key maps, which are usually particularly efficient. String comparison is just integer comparison, although sorting the strings in this way would order them by when they were first seen rather than lexicographically (obviously, on could look up the original string and compare those instead to sort lexicographically).

The critical part about this is always remembering to look up strings in the global table, and not try to send the integers over the network, for example.

Note it is also possible to use a pointer to the string data in place of the integer, but this doesn't serialize well. It is also really easy to confuse regular char* with interned strings, leading to horrendously subtle bugs.

share|improve this answer

Setting the CVar might lag a little, sure, but that's okay - you're already parsing the text input from a command. To optimize it if you must, hashing the name to an int for a quick lookup map is all you need. You could also make a directory structure, EG sv points to the sub-map containing sv_cheats to further minimize lookup comparison count.

It's getting the value of the CVar that you have to keep tightly optimized - and that's easy, just keep a pointer to the CVar, rather than looking it up by name every time. (This is how Quake and other major CVar users handle it internally).

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.