Menus Explored, Part 4

Last time, we saw that despite AMX Mod X’s new menu improvements, the system was still flawed in its implementation. After finishing the AMX Mod X new menu system, I had to implement another one for a proprietary project, and naturally used the AMX Mod X one as a basis.

There first major difference is the addition of more callbacks. In AMX Mod X, all menu actions were pumped through one callback best described as:

void MenuSelectItem(Menu *menu, int client, int item);

The new system adds:

void MenuSelectItem(Menu *menu, Item *item, int client);
void MenuDisplayEnd(Menu *menu, int client, bool cancelled);

Note that a menu being canceled and an item being selected are now separate callbacks. This separation is syntactic sugar, but helps make the API cleaner. The next major change is the addition of broadcast displays. Broadcasting displays a menu to one or more clients and waits for the menu to complete on each one. This is extremely helpful for implementing voting routines. The callbacks for broadcasting become:

void MenuSelectItem(Menu *menu, Item *item, int client);
void MenuDisplayEnd(Menu *menu, int client, bool cancelled);
void MenuBroadcastEnd(Menu *menu);

Additionally, queuing is added. Menus cannot override other menus, and instead get added to a queue. This queue is pricey in both complexity and processing power. Consider a voting menu that runs for N seconds – it must complete within N seconds regardless of whether or not all clients have selected items. This means that if the menu is queued on one client’s display, it must timeout while in the queue. For both Source and HL1 this check is implemented in players’ PreThink functions.

Also, the queue is undefined for some menu situations. For example, let’s say a player’s menu stops being drawn for some reason. Let’s say the HUD bugs out from an ALT+TAB (common in HL1), or another menu that can’t be detected overrides it. If that menu had an infinite display time, and the player can’t select any items, it means the queue will never be processed again. The only real way to solve this is to put an upper bound on menu times. For example, if an infinite menu isn’t processed with 60 seconds, and there are items in the queue, the queue will be processed.

The queue must be guaranteed to properly flush in every imaginable case; that is, if a menu is displayed to a client, an exit state must ALWAYS be passed onto the MenuDisplayEnd callback. Otherwise, memory leaks would be the worst of problems. Menu code is often heavily state based, and if menus don’t end, the next state in a plugin’s logic will never be reached. Over the past year, I have encountered maybe twenty five bugs combined with AMX Mod X’s menu system and the proprietary one. Every single one falls into one of two categories:

  1. The “key to item selection” algorithm fails in some case. This is the algorithm that, given a page in a menu and a key pressed, determines which item was selected.
  2. Some logic error in the menu system fails to call MenuDisplayEnd, meaning a state never jumps, or memory leaks. These logic errors are usually re-entrancy bugs, optimization oversights, or problems with clients disconnecting.

Menus are complicated. Tomorrow, we’ll look at why SourceMod’s menu system becomes frighteningly more complicated.

One Response to “Menus Explored, Part 4”

  1. Zenith says:

    * Scared.

Leave a Reply

You must be logged in to post a comment.