Writing guides and code comments for Doxygen

While our mudlib documentation standards aren't fixed yet, and they consist of stylistic decisions for the most part, a few of the standards are necessary for working with LPC. Additionally, we've included some examples, and a discussion of developing guides which should save others time down the road.

The structure of this post is as follows:

  • Writing code comments for Doxygen
    • Necessary standards
    • Other Useful Standards
    • Sample comments
  • Writing guides and code-free documentation for Doxygen
    • The tailored function reference (i.e., a "group")
    • The code-free guide (i.e., a "page")
  • Remaining questions

Writing code comments for Doxygen

To get much out of Doxygen, we need code comments in Doxygen style. This section discusses some LPC-friendly standards, and additionally some standards we've found useful enough to suggest globally. We shy away, however, from a full elaboration on our standards as many of them are simple matters of preference.

Necessary standards

  1. Object/class documentation should be included before any code statements in your LPC files. It's fine to include some non-doxygen comments before them, but the comments need to be at the top in order for them to get associated with the "class" our LPC filter creates.
  2. We tend to keep our function prototypes and definitions in the same file, and so we use a command, '@cond PROTOTYPES' in a doxygen comment before our protypes, and an '@endcond' command in a doxygen block following them. This creates a block that is conditionally included (see: the cond command) if it's in the list of ENABLED_SECTIONS, and as such keeps our prototypes from causing duplicate entries in the documentation.

Other useful standards

  1. Keep in mind that, while class/object documentation is going to be rarely read by coders who already understand your mudlib, it helps newer coders get a leg up on the mudlib. It's easy to accidentally make these comments less than useful, because they're inevitably written by people who already do have a grasp of the mudlib. Try to think about the comments from a blank-slate perspective, and write them in such a way that it is possible to discern whether the object contains what you need or not based on just the class documentation.
  2. Don't reference the placement of other documentation in the file. This is good practice in general, but sometimes with repetitive queries or other tasks it's easy to refer someone to the documentation in a function higher up in the file. Not only might the file be re-organized later making this irrelevant, but a few doxygen options dictate how functions will be organized in the output and they may not appear in file order. Doxygen, fortunately, allows us to directly link to other functions--and this should be the method you use when you want to refer people to other documentation.
  3. We require all documentation aimed at builders use the '@see' command to link to related functions both within the file and in other classes. Much like efun documentation, this allows newer wizards to find their way around more quickly, even if they don't guess exactly what they needed on the first try.
  4. We encourage comments in plain, brief, formal, grammatically-correct English, at least for functions builders need, and suggest documenters only rely on @commands when the clearly improve the documentation. The commands mostly exist to describe code functionality and they lean on code terms which some builders are inevitably not yet comfortable with; plain language is both less intimidating and encourages actually explaining what goes on.

Sample Comments

Note that some of these examples use an @ingroup command which is discussed in more detail in the guides section.

A very simple example:

1/** @ingroup build_room_exit_uncommon
2Return the string path of a single random exit.
3*/
4string query_random_path()
5{
6	string *paths = m_values(exits, 0);
7	return paths[random(sizeof(paths))];
8}

An example demonstrating how to extend documentation for an inherited function:

1/**
2Room reimplements this description set_ locally to support passing the query up to area objects.
3@copydoc description::set_listen
4*/
5int set_listen(string str)
6{
7	utility_settings["listen_room"] = str;
8	return 1;
9}

A more typical example of build documentation:

 1/** @ingroup build_room_exit
 2Add a functional door (using /std/door.c) in your room. This allows you
 3to link two rooms together with a door having multiple properties
 4(metal/wood, locked/unlocked, open/closed, etc.).
 5
 6@usage{
 7add_door(ROOM+"door1.c", ROOM+"door2.c");
 8add_door(ROOM+"door2.c", ROOM+"door1.c");
 9}
10
11@todo I suspect there's room to refactor doors to be more reliable,
12less of a pain to set up, and less complicated in general while
13maintaining flexibility.
14
15@see utility::add_exit(), utility::set_exit_guard(), activities::hide_exit
16*/
17varargs int add_door(mixed door_file, mixed link_door)
18{
19	if (link_door)
20	{
21		door_file->link_to_door(link_door);
22	}
23	else
24	{
25		find_and_load_object(door_file);
26	}
27	return 1;
28}

A relatively advanced build example:

 1/** @ingroup build_room_exit
 2Assign one or more objects as @a guardians for the exit in @a direction with an
 3overridable @a block_message and an optional @a guard_closure for
 4more control over who is/isn't blocked.
 5
 6Only the @a direction and @a guardians arguments are required, while the
 7@a block_message and @a guard_closure arguments allow for overriding
 8standard behavior without having to overload room functions to do so.
 9READ: please don't overload room functions just to get behavior you
10can accomplish here.
11
12@attention This function now handles more advanced guard tracking
13which makes the historical practice of writing super.specific.npc.ids
14for the purpose of finding the right guard obsolete. Don't do it anymore.
15
16@param guardians May be a single string ID, a single object, or an array of
17either (for setting multiple guards to a single exit). This object
18is usually an NPC, but you may allow inanimate objects to guard.
19
20@param block_message If this is a simple string, it is printed
21privately with `guard->our_targetted_action(block_message, this_player())`
22and only the guard object and the object being blocked will see.
23If, however, you supply a proper action message (this is MUCH preferred),
24it will be printed with `guard->targetted_action(block_message, this_player())`.
25See usage section. May also be set to 0 to keep default messaging.
26
27@param guard_closure The closure argument will be evaluated to see if
28the function should STOP a player (a TRUE return will block, a FALSE
29return will not). I recommend the inline format, `(:$1->query_level() > 10:). The closure is called with the moving player/living as the first argument
30and the guard object itself as the second argument, meaning you can
31reference the player as $1 within the closure and the guard as $2.`
32
33@return The number of guards successfully added, if you need to know.
34
35@synopsis{
36int set_exit_guard(string direction, string guardian)
37int set_exit_guard(string direction, string guardian, string block_message)
38int set_exit_guard(string direction, string guardian, string block_message, closure guard_closure)
39int set_exit_guard(string direction, string *guardians)
40int set_exit_guard(string direction, string *guardians, string block_message)
41int set_exit_guard(string direction, string *guardians, string block_message, closure guard_closure)
42int set_exit_guard(string direction, object guardian)
43int set_exit_guard(string direction, object guardian, string block_message)
44int set_exit_guard(string direction, object guardian, string block_message, closure guard_closure)
45int set_exit_guard(string direction, object *guardians)
46int set_exit_guard(string direction, object *guardians, string block_message)
47int set_exit_guard(string direction, object *guardians, string block_message, closure guard_closure)
48}
49
50@usage{
51set_exit_guard("east", "witch");
52set_exit_guard("west", ({"witch", "monkey"}));
53set_exit_guard("south", find_player("kragan"), "$N $vstop $t from entering $p guild!", (: $1->query_class_num() != NATIVE :));
54set_exit_guard("north", "boulder", "$T $v1try to $v1climb over $n but $v1fail.");
55set_exit_guard("up", ({add_obj(MON+"guard1.c"), add_obj(MON+"guard2.c")}), 0, (:$1->query_level() < 10:));
56
57@see utility::add_exit(), utility::set_exit_mesg(), utility::conceal_exit(), utility::reveal_exit(), activities::hide_exit
58
59*/
60int set_exit_guard(string direction, mixed guardians, string block_message, closure guard_closure)
61{
62	return find_exit_guards(direction, guardians, block_message, guard_closure);
63}

Writing guides and code-free documentation for Doxygen

While code documentation was relatively straightforward, it’s taken us a little longer to get a good grasp of how to use doxygen’s group/page functionality for the most benefit. Our eventual goal, not yet reached, is to move all of our developer-oriented documentation into a doxygen-friendly format.

The tailored function reference (i.e., a “group”)

We realized quickly that full class documentation for build level blueprints was far too overwhelming for new builders. The room object, to use one example, has several hundred methods available internally. We find it useful, then, to define a number of groups with the @defgroup command in a .h file used only for this purpose. We can nest these defined groups by using the @ingroup command as well. Here’s a very brief example:

 1/**
 2    This is the builder's guide. More soon.
 3    @defgroup build Builder's guide
 4*/
 5
 6    /**
 7        This part of the guide is dedicated to building rooms.
 8        @ingroup build
 9        @defgroup build_room Room-building guide
10    */

As you saw in the former section, many functions include an @ingroup command. Functions, unfortunately, can only be included in a single group–so it is necessary to give a long think to structuring the groups when you have something like a standard object module or an ID module included in all of your major blueprints. Fortunately, however, groups can be included in multiple other groups. So you can create the group of only the necessary builder ID related functions, and then nest it into the groups for your room, equipment, and living objects, for example.

The code-free guide (i.e., a “page”)

Sometimes it’s necessary to document things that don’t belong in the code. Rules, tutorials, quality standards, etc. Doxygen provides a good mechanism in pages, and we supply them as markdown files (.md) for Doxygen to process. They aren’t *quite as clean as plain ascii files, but they’re still perfectly human-readable from the MUD side when necessary.

Here’s a short example of a file named qc_style_naming.md:

1STANDARD NAMING CONVENTIONS {#qc_style_naming}
2================================
3- Names shouldn't be sentences (definitely no end punctuation).
4- Only proper names should be capitalized (Tom Bombadil, but not Skeleton).
5- Names should be the shortest complete reference for an object, which means
6    they shouldn't include titles, nicknames, etc.--these are the job of the
7    short description.

Remaining Questions

One of the big remaining questions is whether/how to integrate existing driver documentation (for the master object, efuns, the driver itself, LPC, etc.) into the documentation system. We’re currently in the middle of blundering through an implementation–answering the ‘how’ question–and we can say that leaving these documents in place and and letting your manual command fill them in as necessary is definitely the simplest way to go. That said, we wanted to be able to make good use of doxygen’s cross-references, so on we plodded.

While discussing the details of our implementation is a bit beyond the scope of this post on code comments and pages, the process will be discussed in the next part of the series. Until then, here are three keys to our current implementation, which may get your gears turning:

We need to parse our function-oriented documentation into fake objects which aren’t actually used on the MUD but allow doxygen to index the functions and associate documentation with them. We need to parse our concept-oriented documentation into a heirarchy of markdown pages. We need to translate the existing doc’s “chapter” references into references appropriate for Doxygen.

Discussing this elsewhere?
Enter 'link' for a markdown link
or 'tweet <message>' for a pre-populated Tweet :)
Want to subscribe? Enter 'rss' for a feed URL.
>