The Script Engine in the Create Framework was built so that existing class hierarchies could be wrapped very easily. There is a somewhat deep class hierarchy in the framework, and as such, something that handled inheritance easily and transparently was required.
In many cases, there were arrays of base classes. In C++, handling these is relatively simple: you either treat them as base classes, or you dynamic_cast, etc. The same thing applied with functions accepting pointers to base classes as arguments: you have to either treat it as a base class, or test by converting. I considered using this approach; it would be quite easy to implement, and would be very high-performance. In fact, there are a couple of objects which are still wrapped for scripting using this approach, with isWhatever and whateverValue members.
I eventually decided against using this principle, however. The reason is simple: I am wrapping C++ objects for JavaScript. To C++, I want everything to look and act like C++. To JavaScript, however, I don’t want things to act like C++ — I want them to act like JavaScript.
There is no such thing as a base class in JavaScript. An object is either of a specific type, or it isn’t. (This is a simplification; prototype inheritance allows something like a base class, but the point still stands).
So, when I pass a pointer to a Base to JavaScript, that actually refers to a Derived, JavaScript should know, and should wrap the object with a wrapper for the derived class (if one exists).
Things got tricky very quickly.
I started by creating a type map between types, which were determined using typeid and RTTI, and the wrappers associated with the types. This worked, except when passing JavaScript an object of a type not specifically placed in the map. I only made JavaScript aware of the types implemented in JavaScript, which meant that types derived from these types were not handled automatically.
The solution was a bit tricky, but works: in addition to keeping a type map for caching, I now also keep a type tree. If the cache misses*, the type tree, which stores the types hierarchically, is used to determine the correct type.
Each type in the tree has its own collection of sub-types. Each time a type is searched for, each of the sub-types are checked to see if the type is one of them (through dynamic_cast). Is this slow? Of course. But this performance penalty only occurs the first time any type is used*. After that, it is cached in the type map.
What does this allow me to do? I can wrap an object like so:
//make a new point //but instead of making a Point, make a point of type MySpecialPointType. //JavaScript has never heard of this type, but since it has heard of Point, the base //class for my special point type, things will work. Point *point2 =new MySpecialPointType(); //wrap the point for scripting //automatically wraps it as if it was a Point. SObject object = $(myPoint); //get a function from script SFunction jsFunction = context->get("myFunction")->functionValue(); //prepare an arguments array SArguments arguments; //pass our wrapped point as the first argument arguments.push(object); //call the function, using object as "this" and the first argument jsFunction->call(object, arguments);
* A small snag, at this point, is that the cache does not work. Every time I enable it, it appears invalid entries get entered into the cache. I have not yet had time to debug thoroughly, so for now, it has a minor performance penalty every time an object is created.