DYNAMIC OBJECTS
We've seen how to make class objects constructors and prototype functions. However, it is often desirable to produce objects that are more flexible than a standard object. You may want to map an object to a real entity in your application and have changes to it immediately reflected. For instance, you might want to map an object to your display screen such that when a user writes:
displayObj.background = BLUE;
Your screen changes to the color blue. You do this using dynamic objects. While dynamic objects are most often used to make flexible class members, any object can be dynamic not just members of a class.
Very often you will want to associate one or more C or C++ pointer directly with your object, so that when your wrapper function retrieve that object that can also retrieve the C/C++ pointer. Using seGetPointer() and sePutPointer() along with SE_HIDDEN_MEM or SE_HIDDEN_UNIMEM or with an seInternalizeStringHidden property, is an excellent way to keep the data on your C/C++ side safe from the script code and always associated with the proper objects.
ScriptEase allows you to implement a table of callback functions that you associate with your object using the seSetCallbacks API function. Normally you do this in your constructor when initializing an object of your class. This table has callbacks for all the object manipulation tasks such as getting a member, putting a value to a member, deleting a member, etc.
Your callbacks will override the normal behavior for the object. To implement the above example, you would override the put behavior of the displayObj object. Your code would check for your special property background and changing the screen color to match the color being put to that member. You can override only some of the behaviors by leaving the others you are not interested in as NULL in the table.
Here is a list of the functions in the table. The function prototypes are given for the various functions, when they are called, and what their parameters and return mean. When implementing any of these functions for your own object, remember that SE_THIS refers to the object being manipulated.
SE_CALLBACK( sebool )
get(secontext se,sestring prop,sebool call_hint);
The get callback is used when a member's value is being accessed. It is also used when trying to determine if an object has a property if you have not declared hasProp (see below). Declaring hasProp is the preferred method.
The prop parameter, a parameter to most of these dynamic callback functions, indicates which member of the SE_THIS object is to be accessed. Normally, you use seInternalizeString at the beginning of your program to internalize your special properties, then you can compare them with the property being accessed using a single pointer comparison. The alternative is to turn prop into a string using seGetInternalString then compare with a strcmp_sechar, but this is a lot of work and must be done on each get operation.
call_hint is a boolean indicating if ScriptEase believes the returned value is going to be used as a function to call. This would be the difference between:
a = yourobj.foo; /* call_hint==FALSE */
and
a = yourobj.foo(); /* call_hint==TRUE */
Knowing this information is useful in certain dynamic objects in which a property and a method require different setup routines, such as COM.
Once you've decided what value the dynamic property should have, you return it using the usual SE_RETURN object and return TRUE from the function. If you've decided the property is not one you are interested in, return FALSE. ScriptEase will act just as if the dynamic callback did not exist in this case, looking up the property in its internal storage for the object.
Note that you can access the internal storage of the object within your dynamic callback implementation. You should use the Direct versions of the seGetXXX and sePutXXX API calls in order to bypass your dynamic properties. If you use the non-Direct versions, the internal storage will be used for your object but only for gets. This is because a particular callback for an object is shut off inside that callback, to prevent infinite recursion. However, only that one callback is shut off. If you use the object in a way that uses another callback, ScriptEase will use that callback. On rare occasions, you want that behavior. Most of the time, however, the implementation of a dynamic callback will want to directly access the members of its object. It is usually much clearer and quicker to just use the Direct versions of all ScriptEase API calls while implementing a dynamic callback.
SE_CALLBACK( sebool )
put(secontext se,sestring prop);
This callback is used whenever any of the object’s members is being put to. Like the get callback, the parameter prop indicates the property being updated. The value being put to that property is the first (and only) parameter to the callback, SE_ARGS,SE_NUM(0). Also like get, you return TRUE if you've handled the put operation and FALSE if you still want ScriptEase to update its internal storage for the object in the regular way.
Like the other dynamic callbacks, the put callback for your object is turned off while inside of it. However, it is usually better to make this behavior irrelevent by always using the Direct versions of the ScriptEase API calls inside a dynamic callback.
SE_CALLBACK( int )
hasProp(secontext se,sestring prop);
This callback is used when searching for a variable. ScriptEase maintains an internal list of objects to search when resolving a variable reference. This list is called the scope chain and is described fully in the Execution chapter. This list usually consists of the Activation Object, where local variables are stored, followed by the Global Object. It is easy to add objects to the list by using the with statement. If your object is on this list, this callback will be used to determine if your object contains any property being searched for.
The return value can one of the following:
HP_HAS
The object has the property.
HP_HASNOT
The object does not have the property.
HP_CHECK
Disregard this callback and check in the normal way. The normal way involves calling your dynamic get callback if you have one.
HP_DIRECTCHECK
Disregard this callback and check for the property in the object's internal storage only, do not call the get callback.
SE_CALLBACK( sebool )
canPut(secontext se,sestring prop);
Before trying to put a value, canPut will be called to determine if it is to be allowed. You determine whether or not an property can be updated with this callback. Return TRUE to allow the put. Implementing this callback is most useful if you are not implementing a put callback, because if you are then you can merge the functionality of this callback into the put callback by simply not doing any updating.
SE_CALLBACK( sebool )
deleteProp(secontext se,sestring prop);
When a property of an object is to be deleted, this callback will be invoked. As usual, return FALSE if you want ScriptEase to delete the property from its internal storage. This routine will also be called when the object itself is to be deleted, a destructor. In this case, the prop parameter will be SE_NO_VARNAME.
SE_CALLBACK( void )
defaultValue(secontext se,seDataType hint);
When an object is used in a situation when it has to be converted to a primitive value (i.e. a string or number), this callback is used to do so. The only parameter is hint, the type that the system needs the value as. It is permissible to always convert to a single primitive type, which will then itself be converted to the correct value, if you don't want to take the hint into account. Return the value in the SE_RETURN object.
SE_CALLBACK( sebool )
operatorOverload(secontext se,sword16 op);
ScriptEase implements operator overloading. Whenever an object is used as the left-hand operand, this callback is invoked. The op parameter will be the operator being overloaded, according to this table:
SE_OP_PREINC
|
++expr
|
SE_OP_POSTING
|
expr++
|
SE_OP_PREDEC
|
--expr
|
SE_OP_POSTDEC
|
expr--
|
SE_OP_ASSIGN
|
lhs = expr
|
SE_OP_NOT
|
!expr
|
SE_OP_UNARY_PLUS
|
+expr
|
SE_OP_UNARY_MINUS
|
-expr
|
SE_OP_BITNOT
|
~expr
|
SE_OP_EQUAL
|
expr==expr
|
SE_OP_NOTEQUAL
|
expr!=expr
|
SE_OP_STRICT_EQUAL
|
expr===expr
|
SE_OP_STRING_NOTEQUAL
|
expr!==expr
|
SE_OP_LESS
|
expr<expr
|
SE_OP_LESS_EQUAL
|
expr<=expr
|
SE_OP_GREATER
|
expr>expr
|
SE_OP_GREATER_EQUAL
|
expr>=expr
|
SE_OP_SUBTRACT
|
expr-expr
|
SE_OP_ADD
|
expr+expr
|
SE_OP_MULTIPLY
|
expr*expr
|
SE_OP_DIVIDE
|
expr/expr
|
SE_OP_MOD
|
expr%expr
|
SE_OP_SHIFTLEFT
|
expr<<expr
|
SE_OP_SHIFTRIGHT
|
expr>>expr
|
SE_OP_USHIFTRIGHT
|
expr>>>expr
|
SE_OP_OR
|
expr|expr
|
SE_OP_XOR
|
expr^expr
|
SE_OP_AND
|
expr&expr
|
The assign operators, such as *=, are performed as two separate operations, as if written expr = expr * expr instead of expr *= expr.
The right-hand side of the operator is to be found in SE_ARGS,SE_NUM(0). The result of the operation should be returned in the SE_RETURN object with a return from the function of TRUE. A return of FALSE will do the normal operation which will involve converting the object to a primitive type compatible with the other operand and doing the JavaScript operation.
Note that the operator overload will be called with the op SE_OP_ASSIGN if the object is assigned to. Normally, this operation is ignored since you cannot assign to an object directly. In a script, you can write:
some_obj = 10;
but this just discards the object in the given variable and replaces it with 10. If the object has operator overloading, this will call the overload callback instead. If you return FALSE, the normal changing of some_obj's value takes place. If you return TRUE, it does not. Be careful, you can make a variable whose value the user can never change in this way.
SE_CALLBACK( sebool )
getByIndex(secontext se,int index);
This callback is used to get an object member's value by index. This will be used solely by the ScriptEase API when a programmer is trying to iterate the members of your dynamic object. There is no hint as there is no way to know how the programmer intends to use the retrieved value. Return FALSE if you have no such indexed member.
In order to implement this routine correctly, you need to internally order the members of your dynamic object in a consistent way. A person will be using this routine to iterate all of your members, from 0 on up. You must return each member once only and always in the same index. It is only permissible to reorganize the members if a member is added or removed. Return the member in the SE_RETURN object.
SE_CALLBACK( sestring )
getNameByIndex(secontext se,int index);
A companion routine to getByIndex, this is used when a script wants to iterate through your object using the for..in statement. You must return the names of your object's members according to their index. Like getByIndex above, it is only permissible to reorder your object if a member is added or removed. Return SE_NO_VARNAME to indicate an index beyond the number of members in your object. Otherwise return the internalized version of your member's name (see seInternalizeString). The internalized string will be freed when you return it just as if you called seFreeInternalString. This is useful in the majority of cases in which you create the name to return and no longer need it locked. If you do need to retain a lock on the returned string, use seCloneInternalString to make a duplicate to return.
SE_CALLBACK( sememcount )
getMaxIndex(secontext se);
Return the maximum index of the members of your objects which is equal to the number of members minus one.
For all of the above callbacks, the SE_DYNA_UNDEF flag will cause your dynamic property to be called only if the object does not contain the property in its internal storage. This is useful for speed. When your dynamic put callback is invoked on a property, if that property is not special, you can return FALSE to put it into the object's internal storage. From then on, that property will be treated normally. The properties you are interested in you do not store in the object, you handle them in your callback. They will continue to be routed through your callbacks each time they are accessed.