This document refers to the class CJSObject and its ancillaries in the files scripting/ScriptableObject.h

Objective

The purpose of this code is to automate the process of exposing native engine objects to script.

Outline

The code provides the templated class CJSObject. This class contains definitions of all the functions the SpiderMonkey libraries require for allowing these objects to be used by scripts. The implementation of these functions is such that the script objects exhibit the standard behaviour for JavaScript objects, in that they can be (if a suitable constructor is provided) created by scripts, and that scripts may dynamically add and remove properties of the objects. The script objects are also tied to the lifetime of the equivalent native object, such that if the native object is destroyed, operations on the script object fail safely (the script object itself is not destroyed until all script references to it expire and the JavaScript garbage collector claims it). Conversely, the script object cannot be destroyed while the corresponding native object still persists. The objects also implement the data-inheritance system as required by the Pyrogenesis entity system. They may also, at the programmer's option, be made entirely or partially (on a per-property basis) immutable by scripts.

Benefits

  • It's easy
  • Behaviour of objects exposed in this way will be consistent with that of other objects using the same method. (The dynamic addition of properties is a reasonably useful thing to acquire)

Costs

  • The current implemenation has a high cost in memory, requiring (possibly very large) mapping tables for each object. A proposal to substantially reduce this is mentioned below.
  • Each property access from script requires a hashtable lookup. It remains to be seen how significant this cost is given the existing JavaScript overheads; in any case, some form of lookup is generally unavoidable for interpreted languages.

Operation

CJSObjects maintain a hashtable from property names to (native) property objects. When a property of an object is accessed via script, the name is looked up in this table and forwarded to the relevant property object, which contains methods to read and write the property. There are four types of property objects currently implemented:

Property Objects

Per-instance mapping to native data

The first type, contained in class CJSProperty, is a mapping onto a single variable in the native class (conversions defined in JSCoversions.h/cpp are used to convert back and forth between JavaScript and C++ representations). This property object wraps a pointer to a variable. In addition, an 'update' and a 'freshen' native functions (of type void( void )) can be attached to property objects of this type; the former is called immediately before the property is accessed, the latter immediately after the property is modified (note that function properties (see below) would usually be preferable to use of the update/freshen functions). Update functions are intended to allow the value of a property to be recalculated in the case of the CJSProperty pointing to some intermediate storage location. Freshen functions are intended to allow validity checking to be performed, and for the property value to be propogated if neccessary. Per-instance mappings must (obviously!) be registered for each object, usually in the constructor. Of course, maintaining duplicate mappings in the property tables of each object is expensive, so this form of property should rarely be used. It is, however, the simplest, and the only form of native data mapping that supports data-inheritance.

The terminology used in the code for properties mapping onto native data is 'intrinsic properties'.

The template parameters used in this class are 'T', representing the type of data being referenced by the property (this is needed to ensure that the correct conversion functions are called) and 'ReadOnly' - a class compiled with this true compiles the set-property function as a no-op.

Per-class mapping to native data

This type, contained in class CJSSharedProperty, is also a mapping onto native data, but to a data member of a class rather than to a general variable. The property object itself maintains a pointer-to-member to access this data. As with CJSProperty, 'update' and 'freshen' functions can be supplied.

Per-class mappings are stored in a seperate table, shared between all members of the class; this consumes far less memory than per-instance mappings. Unfortunately, as per-class mappings cannot store per-instance data, data-inheritance does not function with properties of this type, but if this is not required this type of property should be used in preference to CJSProperty.

Template parameters for this class are the same as for CJSProperty.

Mapping to native getter/setter functions

Code for this object type is contained within the class CJSFunctionProperty. This type of property object maps accesses onto one or two native functions; a getter and a setter. The former must have type jsval( void ) and should return the current value of a property. The latter is optional (making a property read-only), but if present must have the type void( jsval ) and should set the current value of the property. Each of these functions may of course perform conversions and validity-checking as neccessary. These property mappings are stored in the per-class mappings table. Function properties are not suitable for properties that must be inherited.

Per-instance mappings to script-defined properties

This type is used for all properties defined by scripts; it stores a jsval containing the current value of the property. It is assumed that no native code will ever need to access this data. The terminology used by the code for properties defined externally to native code is 'dynamic properties'. Code is located in the CJSValProperty class. As properties of this type may be added to one object independantly of others of its class, the per-instance mapping table is used.

Improvements and To-Do

The earliest versions of this code evolved from a refactoring of the common elements of the entity and baseentity script interface code. One lingering result of this is the close ties with the data-inheritance system and the associated overhead, despite the fact that entity and baseentity remain the only two consumers that require this functionality. Seperating out a version of the ScriptableObject system that did not include this would reduce the memory overhead somewhat. Similarly, the data-inheritance system is the primary reason the update/freshen system remains; removing this also would reduce memory and time overhead. I'm afraid I just haven't got around to this yet.

The major consumer of memory is the per-instance property mappings tables. While these cannot be removed entirely, it would be more efficient to use a global (or perhaps per-class) 'dictionary' to convert between a string name and a unique (probably integer) identifier and use this as the key in the per-instance table. It may further be possible to narrow the per-instance table to only jsval properties, and render some of the information currently stored in it redundant. Something I've been thinking about for a while, but it works well enough for PASAP.

A document TODO - this should be updated to include design details for the data-inheritance and property-reflector system, a more detailed usage guide, and the features common to all property objects.

Last modified 16 years ago Last modified on Feb 23, 2008, 4:18:59 AM
Note: See TracWiki for help on using the wiki.