Next: , Previous: Designing levels, Up: Top


14 General design

14.1 Philosophy

14.1.1 Cross-platform

Liquid War 6 is developped on GNU/Linux and other platforms are not supported yet. However there is no reason that would prevent it from running on other platforms, such as Microsoft Windows, Mac OS/X, FreeBSD and in a general manner any *NIX system.

Liquid War 6 uses libraries which are cross-platform (OpenGL, Guile...) so porting it is merely taking the time to fix makefiles and build tools for the given platform. This does not mean it is immediate, but it's a feasible task.

14.1.2 Use the right tool for the right task

There's a balance to find between on one side the temptation to link against every possible library just to get access to a little trivial feature, and on the other side the temptation to re-invent the wheel, either because one thinks one can code it better than others, or because one simply ignore the existence of some convenient library.

Liquid War 6 tries to use external libraries when needed. It makes no sense to try and code using X11 calls directly today, unless you are developping something very X11 related, such as a window manager for instance. Therefore Liquid War 6 uses libraries like SDL to access video hardware.

But to only load maps, and because the map loader has no reason to be linked to the final renderer, Liquid War 6 uses libpng directly, without using the SDL_image interface. It might appear akward at first sight, but it makes sense: there should be no need to link against a complete graphical library when the purpose is just to read a simple PNG file. It is true this has some cost it terms of coding, but the advantage is that it is easier to get rid of third party libraries. Freedom has a price.

14.1.3 Modularity

If look at the liquidwar6 binary you'll find out it's fairly small, but it is linked against (many) convenience libraries like libliquidwar6sys or libliquidwar6ker for instance. One side effect that Makefiles are complex, but experience shows that if one does not force oneself to actually truely separate different modules in a program, that program ends up in a big mess of code. Yes, experience does show this.

14.1.4 Dynamic loading of shared libraries

This is an important point. From the packager's point of view, and also from the user of a GNU/Linux distribution which relies on prebuilt packages, handling different versions of the same executable binary is much less convenient than having an identical core executable binary, which can be enhanced at will by simply putting a .so file in a directory.

14.1.5 Scripting

Ideally, Liquid War 6 would be a hudge script, without any compilation whatsoever.

However, because of obvious performance issues, and some libraries having C interfaces but no Guile equivalent, a significant amount of C code need be written. But the idea is to provide just enough fast C hardcoded functions to keep most of the game logic in the script world, which means scheme code handled by Guile in the case of Liquid War 6.

14.2 Architecture

14.2.1 Convenience libraries

14.2.1.1 Top-level libraries

We describe here the libraries which are directly linked against the main liquidwar6 binary.

There are 9 or them:

Here's some ASCII art to summarize the dependencies.

                  +-----------------+
                  | scheme scripts  |
                  +-----------------+
                           |
                 +-------------------+
                 | liquidwar6 binary |
                 +-------------------+
                           |  
    -----------------------------------------------
   |       |       |       |       |       |       |
+-----+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+
| gfx | | snd | | net | | con | | cfg | | map | | ker |
+-----+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+
    \     /
    +-----+
    | dyn |
    +-----+

                        +-----+
                        | sys | (used everywhere)
                        +-----+

Not everything is represented. For instance, map and gfx both use cfg directly.

14.2.2 The mod-gl case

TODO...

(describe the internal convenience libraries of this module)

14.3 Coding guidelines

14.3.1 Project goals

One of the purposes of Liquid War 6 is to make a cleaner implementation of Liquid War than the previous one, namely Liquid War 5. While the latter has achieved the practical goal of providing a playable implementation of the game, it failed at providing an evolutive platform. Network capabilities where finally added to Liquid War 5, but anyone who played on Internet with someone a few hundreds of milliseconds away would agree that it's far from being perfect. The main reason for this is that it is really had to hack on Liquid War 5, especially when you are not the core developper. The core developper himself, even knowing all the various hacks in the game, is very quickly lost when trying to implement major changes.

To put it short, Liquid War 5 is a global variable hell, a pile of hacks on top of a quick and dirty implementation. Still, it works.

With Liquid War 6, the idea is to take the time to make something stable, something nice which will enable developpers to implement the cool features, and have fun along the way.

14.3.2 Common sense

Here are a few guidelines which I think are common sense advice, but they are still worth mentionning:

14.3.3 Application specific issues

14.3.3.1 Unitary tests

Each of the internal libraries in Liquid War has a “test” program associated with it. For instance liquidwar6sys-test is associated to libliquidwar6sys, and its purpose is to test the features of this library.

While it is fairly easy to test out unitary functions which require no peculiar context, testing high-level functions which requires files, graphical and possibly network contexts to exist is obviously harder to achieve. There's no easy way to draw the line, but the idea is to put in these test executables as much features as possible, to be sure that what is tested in them is rock solid, bullet proof, and that one can safety rely on it and trust that code when running it in a more complex environnement.

These test executables are also very good places to see a library API in action, find code fragments, and make experiments.

14.3.3.2 Memory allocation

The libliquidwar6sys provides macros to allocate and free memory. One should use them systematically, except when trying to free something allocated by another library.

See the documentation for module libliquidwar6sys for more information on how to use the macros.

14.3.3.3 Private and public interfaces

Each library exports a public interface and hides its internal. Since Liquid War 6 uses standard C and no C++, there's no real standard way to handle public/private features. The convention used in Liquid War 6 is to show internal structures as opaque pointers (void *) whenever some function needs to operate on a structure which has possibly private fields. This way the caller function has no way to access the internals, and we are sure that no reference to any internal implementation specific feature will appear.

Here's a code excerpt from src/gfx/setup.c:

     void _lw6gfx_quit(_LW6GFX_CONTEXT *context) {
       /*
        * Implementation here.
        */
     [...]
     }
     
     void lw6gfx_quit(void *context) {
       _lw6gfx_quit((_LW6GFX_CONTEXT *) context);
     }

The function _lw6gfx_quit (note the “_”) is internal, declared in internal.h whereas the function lw6gfx_quit is public, and is therefore exported in gfx.h.

This way, functions in the program using lw6gfx_quit do not know what is in the _LW6GFX_CONTEXT structure, and they need not know it.

This does not mean it is not possible to have public structures, only these structures must reflect some truely public, accessible and safe to access structures.