One of the components of the framework on which I have been working particularly hard in the past week is the Script Engine.
It is what it sounds like: a module designed to allow scripting to work inside the framework. Although most definitely still a work-in-progress, it now has a lot of the features I want it to have.
My goals were:
- It should be extremely easy to make an object scriptable (less than ten minutes)
- It should be extremely easy to pass objects to JavaScript
- It should be extremely easy to retrieve objects from JavaScript
- It should be possible to implement other scripting languages in future
I don’t know about the last, as I haven’t tried it yet, but the first three I think I may have succeeded in to some extent.
Basically, to give you the idea of what it does, I’m going to give a code dump showing how the script engine works. You very likely will not be interested. On the other hand, you could. So, I am posting it.
/* Create a point class */ //Mem is a base class which allows reference counting. class Point : public Mem { public: Point() { this->x = 0; this->y = 0; } Point(double x, double y) { this->x = x; this->y = y; } double x, y; }; /* register Point as a type that might be scripted */ ScriptBaseNative(Point); /* Create a wrapper named Point Wrapper for point */ SWrap(Point, PointWrapper) { public: PointWrapper() { //add the x and y fields this->set("x", $(getX, setX)); this->set("y", $(getY, setY)); //specify that it is constructable constructable = true; //set the class name className = "Point"; } SGet(getX) { //"self" was passed as an argument, so get the point out of it Point *point = self->as<Point>(); //if it is not null, return x if (point) //wrapping it for Script return $(point->x); //otherwise, return undefined return Script::Undefined(); } SSet(setX) { //"self" was passed as an argument, so get the point out of it //the easy way this time Point *point = extract(self); //if not null, set to value, the other argument if (point) point->x = value->numberValue(); } /* Almost identical Y functions go here... */ //called when a script wrapper is created //not when a new point needs creating SConstruct { Point *point = extract(self); //add a reference count if (point) point->ref(); } //called when the script wrapper is destroyed SDestruct { Point *point = extract(self); //return if nothing if (!point) return; //deref and delete if needed if (point->deref() == 0) delete point; } //called when new Point is called in JavaScript SCreate { double x = 0, y = 0; if (arguments.length() > 0) x = arguments[0]->numberValue(); if (arguments.length() > 1) y = arguments[1]->numberValue(); return $(new Point(x, y)); } }; /* Elsewhere */ //create contexts R<GlobalContext> myContext = new GlobalContext(); R<V8Context> v8Context = new V8Context(); //register type myContext->registerType<Point>(new PointWrapper()); //test constructor using v8 v8Context->runScript("var jsPoint = new Point(42, 42);"); //load a function using v8 v8Context->runScript("function something(point){ return point.x; }"); //get the function SFunction f = myContext->get("something")->functionValue(); //prepare arguments SArguments args; args.add($(new Point(4, 2)); //call function double x = 0; if (f) x = f->apply(Script::SUndefined(), args)->numberValue(); std::cout << "The X value of the point is: " << x; //should be 4
It has been a bit difficult to make all of this work, and it is still very much a work-in-progress. One of the largest sources of difficulty was V8. I originally picked it because its Embedder’s Guide looked friendly, but I discovered that there was actually quite a bit that was very poorly documented. The code in v8.h had somewhat more thorough documentation, but I found it lacking as well. What is really needed is several examples, showing the best way to do everything one might want to do.
If you are curious and want to see the code, it is hosted on Launchpad.