Tuesday 5 March 2013

Scripting thoughts

So last night I had a bit of a look at how a javascript thing might work for the game engine. It tempered some of my enthusiasm but I also need to adjust my expectations. Security is a pain, and JavaScript itself turns out a bit ugly, although i can probably do something about that. Strangely the script engine doesn't let you implement new functions for the script? That's probably the weirdest thing about it; you can add objects but not top-level functions. The fact the javascript engine keeps variables around between invocations also makes things a bit more interesting, and not all in a good way.

I had a look at how I might implement a simple existing item: absinthe (it's not a drink i'm a fan of btw so the choice is arbitrary).

The existing item is defined over multiple separate files: the item descriptor and one for each script - 3 total files for absinthe.

Object

Obvious is to implement it as a full object with methods. The object name would match the script name, and all objects could be loaded at once into the script engine.

var absinthe = {
    onUse: function(thing) {
        thing.emote('wails: oh, my head!');
        if (thing.getInte() > 10)
            thing.incrInte(-1);
        thing.remove('absinthe');
    },
    onDrop: function(thing) {
        thing.order("get absinthe");
        thing.remove('absinthe');
    }
};

Drawback here is the scaffolding required. It's almost to the point that you may as well just use Java directly. I will probably do that too.

If the script engine were a proper execution container then things could be very interesting - the state could live inside it too, but I don't want to go down that path.

Script fragments

This is similar to the existing system, separate standalone scripts define each action. They are referenced via names in the objects.

absinthe_use:

    thing.emote('wails: oh, my head!');
    if (thing.getInte() > 10)
       thing.incrInte(-1);
    thing.remove('absinthe');

absinthe_drop:

    thing.order("get absinthe");
     thing.remove('absinthe');

It has similar drawbacks and advantages to the existing system - lots of little files to manage, separated logic, but also isolation and simplicity. It does allow for pre-compiled scripts though.

Script of functions

Here the script just defines top-level functions. It makes it a bit easier for the user to develop as they don't need to include all the scaffolding.

var onUse =function(thing) {
    thing.emote('wails: oh, my head!');
    if (thing.getInte() > 10)
        thing.incrInte(-1);
    thing.remove('absinthe');
};
var onDrop = function(thing) {
    thing.order("get absinthe");
    thing.remove('absinthe');
};

This approach has the drawback that the script must be parsed and executed separately, and must be parsed and executed every time.

Scripted actions

A compromise between the first two is to have a single script but also pass the script action to the script and let it decide what to do. e.g. use a switch statement. i.e. lets one put all behaviour in one file, but 'simplifies' the scaffolding.

switch (action) {
case 'use':
    thing.emote('wails: oh, my head!');
    if (thing.getInte() > 10)
       thing.incrInte(-1);
    thing.remove('absinthe');
    break;
case 'drop':
    thing.order("get absinthe");
    thing.remove('absinthe');
    break;
}

Problem is that it doesn't really simplify the scaffolding, although it allows for precompiled scripts.

Thoughts

One problem is i'm thinking about the scripting system from the wrong viewpoint - more of a library of potentially compiled functions rather than thinking of it as scripts. I need to shift my thinking because they really should just be scripts, and not self-contained applications. If I support Java classes to define behaviour as well then efficiency of the scripting language itself isn't of a primary concern.

The fact that Java can't directly define functions sucks a bit - for example the "thing." prefix to every call. But I guess i can hide that with some simple global functions I inject on my own which hide the details for the main target object.

At this point i'm leaning toward the second option - i.e. much the same as the existing system. But I will look at some techniques for simplifying their configuration such as convention-based lookup rather than needing a pile of settings for each object. A convention-based mechanism would let me support multiple mechanisms without needing to change the file formats.

I might need to let this stew a bit for the moment, and whilst that is coalescing have a look at the multi-map/layer stuff which will be more fun to hack on too.

No comments: