Archive for May, 2006

Backwards Compatibility

Wednesday, May 10th, 2006

One of the things I hate most about software development is backwards compatibility. A large portion of my time spent on AMX Mod X is making sure every release will accept old plugins. All prior plugins must compile and run exactly as they did on previous releases, or else someone will be upset, and rightly so. Furthermore, and ideally, new plugins that don’t use new features shouldn’t break on older versions.

This gets very difficult, especially since the original codebase isn’t ours, and dates back to decisions made over five years ago. That means we have to support plugins five years old, written for an architecture that we didn’t initially design.

And, as much as I hate it, I am quite fanatical about backwards compatibility (as the AMX Mod X Devs can tell you). That’s why you’ll often see code like this (from SQLX):

if (!MF_RequestFunction("GetDbDriver") || 
     !MF_FindLibrary("SQLITE", LibType_Library))
{
    MF_AddNatives(g_OldCompatNatives);
    MF_AddLibraries("dbi", LibType_Class, &g_ident);
}

A module almost entirely unrelated to “SQLITE” must specifically check for SQLITE’s existance. Why? Because the AMX Mod X API was not designed with inheritance/module mixing in mind, and thus, this module must force itself to disable certain features which would otherwise clash with SQLITE.

Bad design is often indetectable early on in a project’s lifecycle; hindsight is 20-20. A good example of this was AMX Mod X’s 1.50 feature of “auto-requiring modules”. This hack placed module names as strings in the “Libraries” table of the plugin header, which was the table’s intended use. When plugins loaded, AMX Mod X parsed the Libraries table, and made sure that each entry had a module loaded with the same name. If none was found, it would display a helpful, but usually ignored message to the user.

However, with 1.75, we decided to have modules automatically loaded. This would reduce confusion amongst users who were lazy (or, in many cases, incapable of following instructions).

Unfortunately, the “Libraries” table no longer captured three important requirements. First, in the original spec, the name didn’t have to match the filename, making autoloading impossible. Second, if this was changed, there would be no way to specify a “class” of modules, like say, SQL. Third, even if the system was changed, the old releases of AMX Mod X could not differentiate a “new library” from an “old library”. So, if a plugin compiled on 1.75 ran on 1.71, the library table’s naming scheme would be incompatible, and produce confusing errors to the user.

I only saw one viable solution to this: force 1.75 plugins to fail loading on 1.71. However, imagine the torrent of forum posts from users using 1.75 plugins on 1.71! This would kill the entire reason of auto-loading modules in the first place: to reduce needless forum posts.

In the end, I decided to abuse another string table available in the plugin file format – an internal table used to store the names of “tags” (tags are essentially data types in Small). To differentiate these hidden “module loading” entries from the tags themselves, I prefixed them with a ‘?’ character, and then made a simple name-mangling scheme to encode how the module should be loaded.

What happened here? The sanity of a perfectly valid table was sacrificed for backwards compatibility. The external, broken API got more complicated internally, and now anyone wishing to support the code must both learn and accept the ugly hacks within.

However, as long as it works, the end user doesn’t care. It’s a necessary evil, but I hate backwards compatibility.