Next: Using the console, Previous: Compilation tips, Up: Hacker's guide [Contents][Index]
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. Of course, this is only a dream, and in the (hopefully "very") long run, Liquid War 6 will also end up as a big unmaintainable mess, like any real-life program, until then, it should remain hackable.
Here are a few guidelines which I think are common sense advice, but they are still worth mentionning:
strcpy
or sprintf
anywhere in the code.
Nowhere. Use their equivalent strncpy
and snprintf
systematically,
as they are part of the glibc and are an order of magnitude safer.
Moreover, Liquid War 6 provides wrappers, such as lw6sys_new_sprintf
which handles all the nasty dirty memory allocation stuff for you;
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 many features as possible, to be sure that what is tested in them is rock solid, bullet proof, and that one can safely 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.
Liquid War 6 provides macros to allocate and free memory. One should use them systematically, except when trying to free something allocated by another library, and in very special cases, mostly concerning low-low level operations which are seldom hacked on.
Usage of macros LW6SYS_MALLOC
,
LW6SYS_CALLOC
and LW6SYS_FREE
is straightforward,
read any random chunk of code, for instance ./src/lib/sys/sys-test.c
to see them in action. They are defined in sys/sys.h
.
Once used, these macros will track every single call to malloc
and free
,
and if there’s a difference, it will report it. It will also help you by
showing what’s in the non-freed memory area, at which line of code
it has been allocated, and when. This is very usefull to track down memory leaks.
Of course a debugger could tell you some of these informations, but experience
shows than when you encounter a memory bug, it’s very often impossible to
reproduce it. So you one wastes time trying to reproduce the bug, whereas
with this tool you have the information reported just when the problem happens.
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.
Basic rules :
To check that a commit does not break everything, a good practice is to
run a make check
before committing / submitting anything.
Then, once it’s within the main GIT repository, check the Jenkins builds to see if the program still builds correctly.
Liquid War 6 is regularly audited with automated tools. You might
need to pass --enable-gcov
to --configure
if you want
to use thes tools yourself. More precisely:
liquidwar6ker-test
or liquidwar6ker-pil
.
Bits of code which depend on other libraries are a different story, for
some projects on which Liquid War 6 depends might, for some reason, raise
warnings. But as far as Liquid War 6 is concerned, the goal is simple: zero leak.
Those tools certainly don’t garantee the code is perfect, but they do help improving the quality of the program. If you hack, it’s recommended you give them a try.
Next: Using the console, Previous: Compilation tips, Up: Hacker's guide [Contents][Index]