Next: , Previous: , Up: Hacker's guide   [Contents][Index]


3.3 Architecture

3.3.1 C + Guile

Technically, Liquid War 6 is a collection of C functions which are exported to Guile. The main binary embeds a Guile interpreter, which will run a Guile script. This script calls the exported C functions, and glues them together.

It should be possible to implement the game without using Guile at all, using C code to make the various modules communicate together. This might seem an easier way to go, not involving several languages. However, using this script level is a good way to achieve several important goals:

Having Guile to implement high-level stuff also decreases, to some extent, the need for object-oriented features of C++. The big picture is : low level code that require speed, optimized data processing, is written in C. Code which is more high level and requires abstraction is written in scheme.

3.3.2 Threading and SMP

Liquid War 6 makes a heavy usage of threads. Early versions of the game did not have this feature but starting with 0.0.7beta, one can really consider the game is heavily threaded.

There’s basically:

So globally, if you have an SMP system, the game will be happy with it. It will also run on a single processor, as the program uses POSIX pthreads it’s capable to run on any computer which has pthreads implemented for it.

But, and this is a strong limitation, without pthreads, the game won’t run. At all. Or at least, not unless it’s almost completely rewritten.

3.3.3 Internal libraries

The C code is splitted into several internal libraries. This allow independant testing of various game modules.

The main module, the most important one, is libker, (stands for “kernel”). This is were the core algorithm is. To some extent, the rest of the code is just about how to provide this module with the right data and environment. Logically, if you profle the game, you should find out that a great part of the CPU time is spent here. Code here is about spreading gradients, moving fighters and cursors.

The libmap module is here to handle maps, it contains the code to manipulate maps in memory. But it does not know how to load them from disk. This is the responsability of another module, libldr, which is linked against libraries such as libpng or libjpeg and does the job of transforming those standard formats into a usable in-memory structure. The libgen module also works the same way, creating pseudo-random maps. There’s still a another moduled involved in map handling, it’s libtsk, whose job is to load a level in the background. It has a 2-steps asynchronous loading system which allows the game to load maps while the user interface is still responsive, and give a preview of the map as soon as possible, when loading continues in the background, building optimizing structures which are usefull when playing but not mandatory just to show the map.

At the other end of the algorithm-chain, the libpil module will “pilot” things. It’s this module which will translate text readable orders (typically adapted for network usage) into function calls. It has event lists, keeps them in the right order, and will also permanently maintain three different states of the game. A backup state which can be used any time to go back in time and get the game in a stable 100% sure state. A reference state which is correct but ever changing. Basically backup plus all the orders received between backup and reference gives reference. And finally a draft state which is as up to date as possible but might be wrong. This is typically interesting in network game, where we want to show something moving, something fast, even if there’s lag on the network and other computers fail to send information in time. In this case we display draft while still keeping reference and updating it when we finally receive valid informations. Backup would be used to send bootstrap information when people are joining a new game, or to check up if things are going right.

A special libbot module is here to handle bot algorithms. A bot is just a simple move function which takes a game state as an input, and returns an x,y position, just the way a mouse handler would. How complex a bot is “under the hood” depends on the type of bot. Current bots are really basic. Additionnally, libsim will run dummy fight simulations to find out wether some team has a real advantage on another one, speaking of team profiles depending on colors.

The libgfx module handles all the graphics stuff. It is itself splitted in several sub-modules, that is, it does not do anything but load a module such as mod-gl1 which will actually contain the implementation. In an object-oriented language, it would be an abstract class, an inteface. The implementation does not need to be thread-safe. It’s better if it is, for theorically it could be possible to fire Liquid War 6 with two display backends running at the same time on the same game instance, but this code has yet to be written, and it’s a rare dual headed configuration which probably has no real-life usage. If only one graphics backend is activated at a time, the rest of the implementation garantees there will never be two concurrent calls to a function is this module. It is the libdsp (“display”) which handles this. It fires a thread for rendering purposes, and sends information to this thread, detecting automatically if it’s necessary to acquire a mutex and update rendering informations. For the caller, this is transparent, one just has to call an update function from time to time. The module will even perform “dirty-reads” on a game state being calculated, to render things in real time, as soon as possible.

An experimental libvox module is under design/development and might, in the future, provide a real-time voxel renderer. Still pre-alpha stage.

To ease up the implementation of different graphics backends, a libgui module contains code which is meant to be used by any graphics backend. It’s just a factorisation module, containing common code and interfaces, related to displaying things. This is where, for instance, one can find a high level menu object. In the same spirit, libmat contains generic math, vector and matrix code, which is commonly used in 3D interfaces.

The libsnd module handles sound. It’s also an abstract class, an interface, which uses dynamic backends as implementations.

The libnet module is a wrapper over different network APIs, it handles Winsock and POSIX sockets in a uniform manner. The libcli and libsrv contain network client and server code, implementing the various protocols in dynamically loadable sub-modules. It’s the role of libp2p to glue this together, handle the list of available servers, the message queue, verifying nobody is cheating, and so on. All this modules share information about current game state using code & structures defined in libnod,use message utilities (format, parse) defined in libmsg and share code concerning connections in libcnx. Additionnally, libdat provides facilities to store old network messages and sort them.

The libsys module contains most system and convenience functions, it handles logs, type conversions, timer, memory allocation, it’s the fundamental module every other module depends on. It has a compation libglb module with all the Gnulib shared code.

The libhlp is used to handle keywords and internal self-documentation (this is what is used by --list and --about), libcfg knows how to read and save config files, libcns handles the console, and libdyn can load .so shared files dynamically.

To glue all this, there are some Guile bindings with helper functions available in libscm which fills two needs, one being an easy way to check if Guile linking is working correctly without requiring all other modules to be available, and also performing automatic checks on some actions such as registering or executing a function.

Finally there are small modules like libimg (to make screenshots of the game) which have been separated because they required special libraries to link with and/or did not really fit in existing modules for dependencies reasons.

So well, this is a lot of modules. The list might move a bit, but the big picture is here. Each module is testable separately.

Below is a Graphviz diagram, which shows the modules dependencies.

Diagram showing modules and libraries dependencies

3.4 Memory structures

The most important memory structures in Liquid War 6 are:

All these structures are defined in the ker/ker.h header.

3.5 100% predictable algorithm

The core Liquid War 6 algorithm is 100% predictable, that is to say, given the same input, it will produce the same results, on any computer. Previous versions of the game also had this property. This is very important for network games, since in a network only informations such as “cursor A is at position x,y” are transmitted. Every node maintains its own internal game state, so it’s very important that every node comes with the same output given the same input.

For this reason Liquid War 6 never uses floating point numbers for its core algorithm, it uses fixed point numbers instead. It also never relies on a real “random” function but fakes random behavior by using predictable pseudo-random sources, implementation independant, such as checksums, or modulos.

There are also some optimizations which are not possible because of the predictability requirement, for instance one can not spread a gradient and move the fighters in concurrent threads, or move fighters from different teams in different threads.

If you read the code, you’ll find lots of checksums here and there, a global checksum not being enough for you never know where the problem happened. The test suite uses those facilities to garantee that the game will run the same on any platform.

Not being able to rely on a predictable algorithm would require to send whole game states on the network, and this is certainly way too much data to transmit. A moderate 200x200 map has a memory footprint of possibly several megabytes, so serializing this and sending it to multiple computers at a fast-paced rate is very hard, if possible at all, even with a high bandwidth. We’re talking about Internet play here.


Next: , Previous: , Up: Hacker's guide   [Contents][Index]