Archive for May, 2005

New MetaEng, Part 2

Sunday, May 29th, 2005

With core2, many object types have been shifted around, phased out, or renamed. One of the most important types that has arisen in new form is “IUnloadable”. Since SourceMod allows you to remove plugins at any time, nightmares would ensue if things like cvars and console commands were not properly removed on plugin unload.

An IUnloadable defines an object that, at any point in time, could be unloaded from memory. Back in core1, an Unloadable was the opposite – something that couldn’t be unloaded. Slight mistake.

So, now what can be unloaded? The core types essential to SourceMod – IModule and IScript – are both IUnloadables. An Unloadable must also have a parent Unloadable – that is, if it is attached to a related piece of unloadable memory, it must specify this through its GetOwner() property.

In this manner, an IScript is an IUnloadable whose owner is the IUnloadable of the IModule who loaded it. Furthermore, any ICallable from that IScript will be an IUnloadable owned by the script. When the base module is uploaded to CVS, classes like ITimer and IMenu will also be IUnloadables.

So, how does this solve anything? PluginManager and ModuleManager will define two new interfaces: IPluginListener and IModuleListener. A manager class, such as CMenuManager or CTimerManager, can implement these interfaces and register themselves with both the Plugin and Module managers. The manager will then be notified of every plugin and module that is loaded and unloaded.

When the unload event is called (OnScriptUnload or OnModuleUnload). the manager can check all of its internal structures. Since an IMenu is an IUnloadable, it should have a valid parent, which can be compared to the pointer of the module or script. If they match, the manager must remove the callbacks from its internal lists, which will prevent it from accessing deleted memory.

With this design, SourceMod now has complete plugin/module/metaeng control. Everything will be dynamically loadable and unloadable at runtime, and unloading the root object will cause a chain reaction of unloading everything.

MetaEng – New and Improved

Sunday, May 29th, 2005

As you may have noticed, SourceMod’s core is being rewritten from the ground up as “core2″ in CVS. Recently I decided that MetaEng was in need of some serious revision. There were quite a few flaws in its over-complicated and inefficient design, part of these which contributed to the difficulty of writing nice code around MetaEngines.

A recap: MetaEng is an API designed to help integrate various scripting language engines under one convention. A MetaEngine is a scripting engine which can load scripts. A script is called an IScript, and can have public/external functions called “forwards”, which can be called from outside programs. A function inside a script is called an ICallable. You can send an ICallable a block of data, which the MetaEngine must decode and send to the script. To ease this mess, there is a class designed to wrap everything together called the ForwardManager. The ForwardManager can do things like call one function in any script, or call a named function in a list of scripts.

Example. Observe these two functions in two different scripts:

//test.sma/test.amx
public entities_touched(toucher, touched)
{
  return PLUGIN_CONTINUE
}
//test.js
function entities_touched(toucher, touched)
{
  return Plugin.Continue;
}

The first script is loaded by MetaEngAMX, and the second by MetaEngJS. However, both return a generic interface called an IScript. To call entities_touched in a script (like an event), it’s simply a matter of getting an ICallable from the IScript. The ICallable can then execute the function through a special platform-independent data structure.

In the old MetaEng, the calling convention was based on a ParamBlk. This ParamBlk contained the essential information necessary to call a function, and was what you needed to give an ICallable for the MetaEngine to decode it for the script. If you read the earlier MetaEng articles, this horrendous structure took the form:

ParamBlk pb = new char**[Params*2+1];

If you were to call the function in 5 plugins, ForwardManager would allocate the parameter set 5 times. Unnecessary? Yes. Furthermore, this structure did not pass atomic/primitive data. It passed pointers, and each MetaEngine had to grub out the pointers to do anything. This involved nasty casts like void * to Param_Type. No.

MetaEng now greatly simplifies this with unions. Observe:

union paramdata_t {
  int iData;		//4 bytes
  float fData;		//4 bytes
  void *pData;		//4-8 bytes
  int64_t i64Data;	//8 bytes
  double dData;		//8 bytes
};
struct paraminfo_t {
  Param_Type type;
  short flags;
  int size;		//array size, <0 if not an array
  paramdata_t data;		
};
struct ParamBlk	{
  unsigned int size;	//number of parameters
  int vararg;		//first vararg parameter, -1 for none
  paraminfo_t *params;	//array of parameters
};

This structure is fairly easy to work with compared to the old one. You can easily get any parameter type without terrible casts. There is also a varargs parameter to support variadic function calling. Furthermore, “flags” has been introduced, so parameters can be marked as arrays or references. This replaces the “PrepareArray” monstrosity, and MetaEngines are now completely responsible for all decoding.

Likewise, the old IForward class has been redone to look more like HL-style messages. In the old IForward, you had to set each parameter type, then tell it which pointer to use. Now, Forwards are initialized with a static function signature – you can’t change it after it’s created. Calling the function looks something like this:

//This is registered once
IForward *g_TouchCalls = 
  g_ForwardMngr->NewForward("entities_touched",
  Exec_Stop, Param_Int, Param_Int);
g_PluginMngr->AddGlobalForward(fi);
<br />
//When two entities touch, we want to call our event...
int retval;
//method 1
g_TouchCalls->SetParamInt(1, toucherIndex);
g_TouchCalls->SetParamInt(2, touchedIndex);
g_TouchCalls->Execute(retval);
//method 2
SIMPLE_EXEC(g_TouchCalls, retval, <br />
&nbsp;&nbsp;toucherIndex, touchedIndex);

As the new “core2″ expands, I will probably make this interface even easier to use.

So what about writing MetaEngines? So far, I have only ported (untested) the C++ MetaEngine, and I’ve only ported it to the x86 platform. The assembly was actually fairly easy the second time around with a better data structure. Look forward to a messy JS and AMX port though :)

Tune in tomorrow for the other new feature of MetaEng: Unloadable modules, plugins, and entire metaengines.

Metamod: Source

Friday, May 6th, 2005

What we have been preparing for some time now is finally finished.

Valve wanted the server plugins interface to be a replacement for the old metamod. But as you might know, it is a very poor replacement – you can only supercede three functions. Plugin developers wanted more power, so they started hacking the vtables. But everyone did it his own way, the plugins did not like each other, and were often not portable. We wanted to provide a unified interface, so we’ve decided to create SourceMM (Metamod: Source).

SourceMM is a gamedll wrapper, just like metamod was. You specify SourceMM as your gamedll, and it then loads the original gamedll. SourceMM contains a plugin manager, a log subsystem, and a hook manager.

SourceMM plugins have much more power than original server plugins. They can easily hook any virtual function from any class instance by name (of course, if you have the class definition ;) ). A easy to use metamod-like interface (with META_RETURN, etc.) is provided for the hook handlers.

We hope that as many plugin developers as possible will convert their plugins to SourceMM in the future. SourceMod will also be a SourceMM plugin.

So visit sourcemm.net and download the Release Candidate 1 now!