Archive for September, 2005

HL2SDK Mistakes

Thursday, September 29th, 2005

Since AMX Mod X 1.60 has been released, I consider it “feature complete” for our Half-Life 1 goals. I’m slowly getting back into Source development again. While I’m busy redesigning MetaEng again (some very big, very cool changes to the architecture, including a Meta-JIT!), I decided to come up with a list of things no one likes about the HL2SDK.

Today’s excerpt is on the versioning system HL2 interfaces use. The problems with this have been seen before, most notably from the early HL2MP release and the very recent DoD:Source release. The idea behind interface versioning is twofold: You want to maintain backwards compatibility, and you want to make sure no one requests something they weren’t meant to. Most of the problems fall into category 1.

As an example, let’s pick apart Valve’s system. Each DLL has a single entry point: CreateInterface(). This is a simple factory function designed to return a singleton instance of a class. Each class has a specific, null-terminated name defined in its header. An example of this is public\eiface.h:

#define INTERFACEVERSION_VENGINESERVER "VEngineServer021"

As you can see, the versioning is embedded entirely into the class name – VEngineServer is at version 021 (which technically would be Octal in C++, so we’ll just call it 21). As the SDK itself says, these interface numbers should be bumped when changes are made. Is this the right idea?

The answer, unequivocally, is no — for a number of reasons. I’ll split this into two sections, both dealing with compatibility. The first major reason is obtaining the interface pointer. To obtain the interface, you must have both the name _and_ the version. For example, if Valve were to release “VEngineServer022,” requests for VEngineServer021 would break. The only way around this, on the user side, is to do something nasty – that is, keep requesting versions until you hit a version that exists. As stated earlier, the goal of interface versioning is to protect against interface mismatches — backward compatibility. The problem with this string method, however, is it does not accurately reflect how the interface has changed. This means plugins requesting the current version of something can fail to find it — something very bad for backwards compatibility, especially if Valve doesn’t release an SDK! (Furthermore – a Valve employee told me Valve isn’t obligated to release the specs for their 1st party engine to mod interface additions, something I’ll get into below).

Consider: Your plugin happily thinks it is compatible with IServerGameDLL and goes about using it mod-independently. Valve then updates DoD:Source for ServerGameDLL004 – your plugin has lost backwards compatibility! This would be acceptable if the ServerGameDLL class was changed in a way that wasn’t backwards compatible, but it wasn’t — only two new functions were added to the end (meaning the original vtable is intact).

This is good for the HL2 engine itself, which has to know what version a mod is using (lest it call a function that is garbage), but terrible for their own plugin design. Furthermore, there is a glaring contradiction – Valve has updated VEngineServer to add a new function, but not bumped the version number. Why? Because all 3rd-party software would break in one go, unless Valve exposed two copies of the engine with a new name, similar to how the engine already searches for multiple interfaces.

That’s the first half of it – the second half is Valve’s own policy toward versioning. As said above, they often change interface versions for their own first-party mods without releasing the changes. This was originally the case with PlayerInfoManager, which was version 002 on CS:S and 001 on HL2MP. Worse yet is the DoD:S problem, where Valve is now using two different versions concurrently, and Valve hasn’t even posted the interface changes publically. Therefore, people must either assume that the 003->004 version jump maintains backward compatibility (which a version increase should always do, but the actual design doesn’t implicate, as you must know about the future version in advance), or that it has changed and you can’t support it. Either way, you’ve lost both proper backward compatibility with future versions, and you’ve lost mod-independent support, as Valve has stated that their interface design doesn’t always have to be public, but worse, they can change it at any time to suit their first-party mods.

It’s ugly. It’s a mess. In short, it’s the HL2SDK. As a short comparison of a better concept, I’ll briefly go over the SourceMod design. Each class implements a base interface called CInterface. This requires two functions to be exported – GetInterfaceName() and GetInterfaceVersion(). Using this, you can request a class by name for any version, and then both the requester and requestee can check to make sure the version numbers meet set specifications. To change an interface in a way that breaks compatibility, you would THEN change the interface version string – for example – IMyInterface2. In Valve’s design, it would mean the Engine could check whether to call newer functions on the GameDLL, or plugins could check whether an Engine version supported the latest construct. Instead, we’ve got to deal with a poorly thought out design.