Joel's Lightweight Code Gen spells SUWEET for small scripting languages in games.
Reading Joel's blog and having lunch with him are two different things. You never really see all of the possibilities of a technology until you see the twinkle in someone's eye and realize that the technology might be slightly more powerful than you originally realized. Today I want to cover function based scripting languages and how these can easily be implemented using the LCG inside of any existing MUD or MMOG system.
The syntax for small scripting languages is not similar to what you see with C# and VB .NET. Small scripting languages are generally functional and not necessarily centered around the creation of objects. In the game world, a developer might create new functions in order to perform specific actions. Each of these functions is then attached to a game world object and fired as part of an invocation list as needed. Code is not used to define everything in the game world. Invocation lists might be stored in a data file with a list of object id's, and their attached function id's. Object's might not even be hard objects, they might just be scalar data or structs. The data something contains, might also not readily identify it's object type, but instead the functions it handles. Let's take a sample case.
> You see a mossy rock in front of you.
Mossy rock? Why is it mossy? Why is it called a rock? You might think someone created a mossy rock object (MossyRockObject : MUDObject) somewhere and compiled it into the game. This is probably not the case. More likely is that they attached the GetName function and that function is diving into the database or some other data file to get the name of the object. Is mossy even part of the game? Or is that something else? Maybe the GetName method is calling a GetAge function or the game engine is calling a GetAge function. In this case the possibilities of getting a name could chain together a large number of similar functions that all return pieces of the objects name.
// Note all functions are assumed to turn return an Object data type and the
// engine is capable of handling the rest. I'm making this very JScript like.
function GetName(ObjectID) {
var name = DatabaseName(ObjectID);
return name;
}
Now, I'd actually have to attach that function after I made it. Each function could be in it's own file. I could store them in XML. I could pre-parse them into some byte-code type structure that I could quickly convert to IL later. Lots of options. So we add a command to our MUD or MMOG that allows us to attach a function to an object. Remember that mossy rock? Well, in our case we simply told the game system to create a new MUDStaticObject. This object is derived from MUDObject and has properties that make it act as a static object within the game world (aka a rock isn't a mobile). Once we create the object, we have an ID. The UpgradeObject {id} {function name} is now capable of attaching a new function. The system is loose. The new function is now something that can be called by name, or it can be added to any of the objects invocation lists. An invocation list is important, because we want the game engine to be able to generically support something like name's, but not necessarily only through the GetName function (note we could have allowed many instances of the GetName function, one per object, but I'm choosing invocation lists instead ;-).
An invocation list for our object looks something like, invocationListNames = { functionIdGetName }. Note there is only a single ID when grabbing a name. We might not even call this a list but instead an invocation bind point so that we have the ability to limit the number of functions for this particular feature to just one. If we wanted to upgrade our name from say “rock” in the database to “mossy rock”, we just swap out the methods.
function GetExtendedName(ObjectID) {
object = GetObjectByID(ObjectID);
name = object.GetName(ObjectID);
age = object.GetAge(ObjectID);
prefix = “shiny “;
switch(age) {
.... // Change prefix
}
return prefix + name;
}
That was easy. Dynamic methods make the above possible. My MUD can take textual input through a world builder, create this new method, and then at run-time, when I compile the method I can convert my script to the appropriate IL to make things happen. In the case above, I'd still have to use UpgradeObject to attach GetExtendedName and then add it to the invocation bind point for grabbing somethings description. That isn't a big move. Then we use some global functions GetObjectByID to surface a structure capable letting us call methods that we think might exist on our object. We could have just called GetName, but what if that function didn't exist on the object? With the object. syntax we are adding some checks in there to make sure that method has been attached. If not you can exit out of the script or do whatever else is necessary.
I'm getting pumped guys. This is the kind of interface I've been waiting on for a long time. It is the type of interface that will allow Terrarium to become more functional and provide an easier to replicate sand-box. It will certainly create an easier to create plug-in framework environment. Whidbey is Whidbey though, and that means some time before you get to produce production level apps on this stuff. I'm going to try and go back and fulfill a promise I made months ago to actually complete my plug-in framework documentation that would allow you to create strong plug-in environments today.
References:
[1] A fun lunch with a couple of Rotor fans
[2] hello, world... LCG (Lightweight Code Gen) style!
[3] Late-bound invocation notes - CallVirt, Delegates, DynamicMethod, InvokeMember.
Note that I didn't actually cover 3. However, this article provides an EXTREMELY fast solution to some common scripting problems. I'll try and blog about why later when I get more time.