Introduction to the new flag system

Submitted by javelin on Mon, 2003-01-27 12:39

This posting should eventually make its way into the Guide for Gods, when that moves here as a collaborative book, but until it does, I thought I'd share some information about the new flag system - how it works and why. I'm going to try to keep this at a conceptual level, but some experience with programming is probably useful.

The old way

Until PennMUSH 1.7.7p6, an object's flags were stored as two 32-bit integers. One integer stored "generic flags", like WIZARD, that can be set on any kind of object. The other stored "toggles", flags that only applied to specific types of objects. Each flag and toggle had a specific bit position within their integer.

This system provided room for 32 generic flags and 32 toggles for each object type. Object "type" itself was treated as a generic flag, so there were practically only 28 generic flags available. And PennMUSH used 25-27 of them itself, leaving little room for people to add their own flags. As a result, two things happened:

  • Tortuous hardcode approaches to quasi-generic flags by making toggles that could apply to multiple object types
  • Tortuous softcode approaches that usually involved using a visual attribute to store flags. (Actually, these approaches were and are pretty good, but did require more work of the softcode and the server)

The primary goal of the flag system rewrite was to remove this limitation. A secondary goal was to make it easy for Gods to add new flags that would just be tested in softcode, and for pennhacks to add new flags with hardcode behaviors.

The new way

Under the new system, flags (and toggles - there is no longer a distinction) are internally stored as variable-length arrays of bytes (a byte is 8 bits). The system keeps track of how many bits are currently in use. When a new flag is added, the system checks to see if there's a bit available in the last byte of the flag array. If so, the flag gets assigned to that bit. If not, the array is grown by a byte, providing room for that flag (and 7 others).

These bit assignments are arbitrary and temporary - they are not fixed positions, but are subject to change (typically, when the server is rebooted). They should never be referenced or manipulated outside of the functions in flags.c. Everywhere else in the code - and in the database when it's written to disk - flags are represented as a string of flag names, like "WIZARD TRANSPARENT". (This actually does impose a limit on number of flags, as the typically MUSH string buffer is 8192 characters. If your flags average 10 characters long, you are limited to about 740 of them. That seems like enough for now.)

As I mentioned above, toggles are no longer distinguished from generic flags. Each flag now includes information about which object types it can apply to. This eliminates the kludgery that used to be required to make a flag like LISTEN_PARENT, that applied to all types except PLAYER.

Other information that goes with each flag is its associated flag character ('W' for "WIZARD"), any flag aliases (like "INHERIT" for "TRUST"), and the permissions required to set or clear the flag.

Under the new system, flags can be created without associated flag characters; that's important, because it makes it possible to have more flags than characters. Flags without a character don't appear next to an object's dbref when you look at the object or in its flags() list, but do appear in examine and can be retrieved with the lflags() function. Two additional functions, andlflags() and orlflags(), allow for testing flag combinations using lists of full flag names.

Of course, there's a trade-off for this flexibility. The new system is more memory-intensive and slower than the old system. Fortunately, computers have sped up considerably, and the difference is unlikely to be noticeable. To help matters a bit, an object's type (player, room, etc.) is no longer stored as a flag. Instead, it gets its own integer in the object structure, which speeds up type checking, a very frequent operation.

The definitions of the flags themselves are now dumped to the same database that contains the objects.

@flag

Once flag bit assignment became dynamic, it was possible to allow God to add flags on the fly, from within the MUSH. The new @flag command provides this ability. It can also provide some interesting information about flags.

Because flags are now stored in the database, @flag only needs to be run once - not at every startup - to add a flag. Another consequence, however, is that if your server should crash before it dumps, the new flag won't be in your database and may need to be re-added.

@flag/add (and most other flag manipulation commands) is restricted to God, largely because you don't want Wizards to accidentally muck with the permissions on the WIZARD flag, etc.

add_flag and flaglocal.c

Pennhacks who write contributed patches and systems may also want to add flags to a game. For example, a space system may need a SHIP flag that it manipulates in the hardcode. One apporoach would be to require users to add the necessary flags with @flag, but it's also possible for the patches to introduce the flags themselves.

The new flaglocal.c file (copied from flaglocal.dst) provides the local_flags() function, which is called after the flag table is set up from the database. From this function, patches can call the add_flag() function to add a new flag at startup (see example in flaglocal.dst). It is explicitly safe to call add_flag() when the same flag is already in the database, so you don't have to test if you've already added the flag on a previous startup - just try to add it.

Some subtleties

Flags are a critical part of the PennMUSH code and appear in many places. Here are few of the intricacies involved in the flag rewrite.

Converting old databases. We do this by remembering how the old flag system used to handle flags (this nostalgia lives in hdrs/oldflags.h and in the flag table in flags.c, which is no longer used for anything but conversion). If we're loading a database that doesn't include flag definitions, we initialize our flags from the old flag table. Thereafter, the db will be dumped with the flag definitions. If you had already hacked new flags in to your flag table, this will also pick up and convert these flags! (As long as you move their bit definitions into hdrs/oldflags.h).

Macros. Several of the common lower-level macros like IS() and all of the flag macros (like Wizard()), had to change to use the new functions to look up the existence of a flag on an object (has_flag_by_name()). Examples abound in hdrs/dbdefs.h

Command restrictions. Because commands can be restrict to objects with certain flags, we now need to load the flag table, and therefore the db, before we initialize the command table and before we read any restrictions from mush.cnf. But we needed other mush.cnf directives loaded before we load the db, so we now take two passes through mush.cnf. In the first pass, before the db read, all directives except command_restrict, command_alias, function_restrict, function_alias, and attribute_alias are read. After the db is read and we have a flag table, we initialize the command and function tables and do a second pass to pick up those directives and apply them. You may notice the multiple passes in log/netmush.log.

Summary

I hope this has been helpful in explaining some features of the new flag system. If you have any questions, you know where to find me.