MetaEng – New and Improved

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.

5 Responses to “MetaEng – New and Improved”

  1. Astroman says:

    People don’t care. When’s sourcemod coming out.

  2. BAILOPAN says:

    I care, and guess who matters the most?

  3. ViPer says:

    I like to read about your guys progress even thou i dont always understand everything like the programing theroys and code, but as i said its still intressting to read :) keep up the good work

  4. atdt says:

    yaaay! we have structs now :)

Leave a Reply

You must be logged in to post a comment.