The keymap.ini file is read at startup and the values are indeed stored in memory (by my calculations, it takes 1568 bytes + various overhead, essentially less than 2k).
Well, the asserts are only compiled into the code if you are making a real debug build -- more specifically, if you have the preprocessor manifest constant "DEBUG" (without the quotes) defined. In gcc, this is defined with -DDEBUG and it doesn't need a value, it's just defined. When an assert() statement is compiled when DEBUG is not defined, it is completely compiled out (i.e., produces no code at all). Thus, it's a good way to add sanity checks that don't have any impact (code size, speed, etc.) in a release build. If the expression contained in an assert() statement is not true at run-time, then the failure is written to standard out and a breakpoint interrupt is set, on x86(_64), this is "int 3" (software interrupt number 3).
The sheer size of the file input.cpp is due to trade offs. I could write a set of functions to do this, but it would be slower and most likely make for larger code. I suppose this was more true when the KeyCode type could be stored in a single byte, unfortunately there are more than 256 values now, so I changed it to a short. Even so, each line in the initialization of the native2kc and kc2native arrays only takes 2 bytes, even though there is much more text than that. If you notice the "#pragma pack(push, 1)" and "#pragma pack(pop)" that surrounds the initialization, that's what tells the compiler not to align fields as it normally would (usually on a 4 byte barrier), but to pack them together. Further, that data is never loaded into memory more than once (only when that data segment of the executable is loaded). Thus, the mechanism I've employed here has the following benefits:
- smaller executable file
- less memory used at runtime
- fastest possible mechanism (actually, it could be faster to not pack the fields, but would take 2-4 times the memory! and then we get into the CPU cache hit question)
and the following costs:
- huge code file
- huge pain to maintain - I actually created this code in an OpenOffice spreadsheet!! Any changes (especially inserting values) will require tedious work to make sure everything still lines up (more so in the array initialization, the asserts just use the symbols and will make sure we didn't mess it up)
In the end, I decided that the benefits outweighed the costs and went with the smaller faster code.
So that part is just input.h/cpp and the classes Shared::Platform::Key and Shared::Platform::Input. The Keymap is implemented in glest_game/gui/keymap.h/cpp. The fact of the folder being "gui" and not "ui" makes it a little odd, but it is part of the user interface, so this appears to be the most appropriate place for it.
So the keymap allows 1 or 2 key combinations (KeyCode and modifiers) to be mapped to a command. KeyCode is an enum defined in shared/include/platform/input.h and is essentially a platform-neutral / GAE-specific definition for each key that is compatible with both SDL and windows. The modifiers for the Keymap are defined in the BasicKeyModifier enum, a different one used in the input.h/cpp which may or may not be a good idea, but it's the way it is now (and can be changed/healed/refactored later) and is a bitmask rather than a true enumeration. Bits can be ORed together, etc. So each Keymap::Entry object specifies both a Key and the modifiers (i.e., shift, control, alt, etc.) and each command can be mapped to up to two key combinations, so I created an EntryPair class to encapsulate that so the pair of entries can be dealt with as a single entity. These are the classes that are used during execution of actual game play.
Then you will notice that there is a UserCommandInfo structure with another initialization table in keymap.cpp. This is used for initialization of default values. You'll notice that I packed these as well, that's to save space in the executable and memory, although this is only read when the game starts up and that memory page can be unloaded once game play starts (and your OS's kernel might do just that if it needs the memory).
So then that just leaves the actual mechanism used for everything
vector<EntryPair> entries;
map<Entry, UserCommand> entryCmdMap;
the classes vector and map are from STL (Standard Template Library). The vector is essentially a dynamic, resizable array and the map maps a "key" object to a "value" object. So the "entries" vector contains one EntryPair object for each command and is mapped to command such that entries[cmd] will be the EntryPair for that command. The "entryCmdMap" maps each individual (non-empty) Entry object to a command, as a sort of cross-reference. This way, I can look up the EntryPair for a specific command quickly (using entries[cmd]) and I can also specify an Entry (key combination) and find out quickly if any command is mapped to it or not (using entryCmdMap[Entry(keyIPressed, modifierKeysDown)]). The "entries" vector is a sort-of master table and the entryCmdMap is essentially an index and it's rebuild by calling Keymap::reinit(), which once there is a UI for editing mappings, will need to be done each time after mappings are changed.
So the rest of the code is basic, straight-forward OO stuff, the Keymap constructor calls loads default values, the EntryPair constructor accepts the UserCommandInfo struct to initialize its self, the Keymap::load() calls EntryPair::init() which in turn calls Entry::init(), etc.
I hope that helps it make more sense.