The SourceMod menu system, now (mostly) finished, has its own wiki article, which is where most of this article will be directed to.
SourceMod’s menu complexity is effectively doubled because unlike the other menu systems mentioned, SourceMod needs to have one API that works for multiple systems at the same time (Valve-style menus and Radio-style menus). The trick is that both systems share very similar code with subtle changes. Finding a good way to abstract this was very difficult and took many rewrites.
In the end, we went for three layers of objects.
- Styles, which are essentially mini-menu systems, detect item selection and are responsible for creating the other objects with their style-specific functionality.
- Panels are low-level API for designing a menu without having to know how to call the underlying API (such as the ShowMenu message or using IServerPluginHelpers).
- Menus are high-level API similar to AMX Mod X’s “new menus.” You can add items and an algorithm will display them such that every menu has a similar, consistent feel.
Although all of these objects derive from a base type, they are each responsible for implementing the API correctly; which means the complexity has doubled. However, there are a few cases we caught ahead of time:
- Menus can be canceled such that no new menus can be displayed during the cancellation process. This is a huge re-entrancy feature that no previous system did, and it led to some weird infinite menu situations.
- Menu are NOT queued. Because of drawing bugs with the HUD, it is possible for menus to disappear without ever sending a callback command. Thus, it would be possible to permanently stall the queue while the client is unable to select anything. Introducing a queue would mean introducing an arbitrary de-facto timeout for all menus. It would also further add to the API complexity and introduce usability questions like “should the menu be appended to the end or the front of the queue?” In the end, it suffices to:
- Make all menus overwrite each other.
- Make sure the API will tell menus when they’ve been overwritten.
- Add functions to see if a player is currently supposed to be viewing a menu.
With this functionality, authors can be respectful of other menus and wait to display for unimportant actions.
- Callbacks are tied to menus once again, unlike the proprietary system but like AMX Mod X. This had to be done for plugin unloadability. If a handler wasn’t tied to a menu, you could display someone else’s menu with your own functions. When your plugin unloaded, Core would have to do a deep-sweep of everyone’s menus to make sure no handlers were owned by your plugin. (In fact, this already has to be done for Panels!) Instead since Menus are Handles, they will get automatically canceled when the owning plugin unloads, along with their callbacks.
- The Menu Manager makes no attempt to interfere with styles. All of the drawing, preparation, and selection key detection is done by styles alone. The only thing the Menu Manager bothers to do is provide an algorithm for adding items to menus in a paginated way.
- There is no “page key to item” algorithm like in the previous systems. Consider the fact that when rendering the menu, you know exactly which items are going to be bound to each key. We took this and gave each player a “selection” array which maps each key press to a potential item. Now, item selection is O(1) with absolutely no complications whatsoever. There should never be any bugs with this, whereas the old system has continual problems.
While SourceMod’s menu system is much more complicated, a lot of this complication came from simply figuring out the design. When all is said and done, it is much easier and more reliable to work with.