Architectural patterns

How to make your code manageable

The puppet master

The aim of this article is to present the architectural patterns proposed by Umbra, but possible to implement in any application.

Engine-Adapter, or the Master of Puppets

The basic design pattern proposed by Umbra is the one I like to call the Master of Puppets, for the lack of a better name. The basic concept behind it is the division of the game code into one or several adapters, called UmbraModules, that are all run and managed by a singleton master object, the UmbraEngine.

The engine, acting as the puppeteer, pulls the strings of its puppets, the modules – that's how the whole idea can be descriptively presented. The engine takes care of the game's internal loop, during which it does not perform any game-specific tasks, but only operates on the modules.

In each iteration, the engine does the following tasks:

  1. Deactivate the modules in the toDeactivate list,
  2. Activate modules in the toActivate list,
  3. Clear the screen (if necessary),
  4. Collect mouse and keyboard events,
  5. Iterate through all active modules and ask each one of them to:
    • parse the keyboard input using the collected events,
    • parse the mouse input using the collected events,
    • update their internal logic,
    • render whatever there is to render in each module.
  6. Flush the screen (showing whatever the active modules have rendered).

The modules, or the puppets, are adapters, meaning, they all implement a unified interface that is manageable by the engine. They implement at least four methods that will be called in each and every iteration of the game loop by the engine. These methods are responsible for:

  1. keyboard parsing in the given module,
  2. mouse parsing in the given module,
  3. the module's internal logic updating,
  4. rendering the module.

The implementation of each of these methods is up to the developer, so they can depend on any resources available to the module, as well as use any methods that are implemented in it.

A possible configuration of the modules might be:

  • The player character. Takes care of the PC's input, updates his/her position on the map and sends interaction requests to other map elements and/or actors.
  • The actors. Holds the list of actors and updates their artificial intelligence, deciding on their actions.
  • The map view. Renders the playable map, along with the PC and the Actors in the PC's view.
  • The message display. Holds the message buffer and displays the last logged message.
  • The stats display. Displays all the relevant data about the PC (stats, health and so on).

The Input-Processing-Output module architecture

The main idea behind how a module works is the clear division of the phases of its operation. The module works in three semi-independent phases. These phases are:

  1. Input parsing and immediate reaction (for instance, receiving a key press and performing an immediate action corresponding to this particular keystroke),
  2. Updating the internal logic,
  3. Outputting the data after it has been processed and possibly modified.

The input phase is obvious, thus I will skip te description.

The processing phase operates on any data that is required and available. This data should be stored insode the module that's updating itself or, if necessary, in external objects that are not modules. Retrieving data from other modules is highly discouraged in this phase since it might change before the rendering phase is reached.

The output phase doesn't process any data, but only uses it to generate a display. It is perfectly safe to fetch data from other modules at this stage, since the processing phase of all modules is already done. On the other hand, data modification, although technically possible, is highly discouraged – again, because there still might be modules depending on it.

Advantages

The advantages of the use of such patterns are rather obvious, but let me enumerate them nonetheless:

  • Code recycleability. Since the engine is completely independent from its modules, it needs to be written once and only once. It can then be used for multiple projects. Also, the modules are reuseable, for instance, the PC module might be implemented in various games, with no or minor modifications (adaptations to a particular game's requirements).
  • Code replaceability. A faulty module can, in most situations, be removed and replaced safely without breaking the entire game. For example, the stats display can be deactivated or completely removed from the game and it will probably not stop it from executing – the stats bar will simply disappear. The module can then be replaced with something else. For instance, there might be two or more versions of the stats bar display modules for the end user to choose from.
  • Code independence. Since modules are largely independent from one another, it's possible to work on one module without having to worry about breaking something in another one.
  • Code readability. Thanks to a unified architecture, the code is more readable. This makes the code easier to manage, even in case a new developer joins the development team or takes a project over.

Disadvantages

As an advocate of the described patterns, I don't see any obvious disadvantages. Still, I guess there might be a slight performance drop, especially in case there are many small, but specialised modules, for instance, an AI director that does not parse any input or render anything. Its rendering and input parsing phases are still triggered, returning immediately – but still wasting some CPU power on function calls. This is only a hypothesis, since no benchmarks have been made to prove or disprove this.

Another problem that might appear is the increased memory useage. The increase might not be tremendous and will probably be negligible on desktop machines, but if the architecture were to be used on a portable device, usually restricting memory useage to a ridiculously small amount, it would probably require some extra brainpower to successfully implement it.