create blog

go home go home
  1. about
  2. code
  3. wiki
  4. blog

Type Trees

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.

Leave a Reply