External Type Registration - KidneyThief/TinScript1.0 GitHub Wiki
TinScript contains just a few basic types, common to many languages, and needed for basic functionality. However, it is likely that if the language were extended to include additional types custom to a specific solution, then the language would be considerably more useful.
To this end, I've taken a common class, but a non-first class value, and added it to the type registration for TinScript, as an example.
To demonstrate using a common class in applications that frequently specify a position or 3D vector, I've implemented a very simple version of CVector3f, a struct containing 3x floats representing an (x, y, z) coordinate or vector.
The class is implemented within /external/mathutil.h, and likely would be replaced with a more complete/robust and custom implementation of this class, when integrated into a solution.
The registration of this class as a first-class value (e.g. can be passed as a parameter, returned as a value, or assigned to a variable) required the following steps:
- Modify /source/TinTypes.h
- Add a VarTypeEntry() to the VarTypeTuple, specifying:
- The type name to be used from script (in this case vector3f)
- The storage size in bytes (3x floats is 12 bytes)
- Had this type been larger than 16 bytes, the macro MAX_TYPE_SIZE would need to be increased
- A function to convert from this type to a string
- A reciprocating function to convert from a string back to this type
- The actual class containing the implementation for our type.
- The function to "configure" the type, specifying operations and conversions
- Add a VarTypeEntry() to the VarTypeTuple, specifying:
- The file TinTypeVector3f.cpp contains all the implementation related to registering CVector3f as a type:
- The implementation of the Vector3fToString() and StringToVector3f()
-
Vector3fOpOverrides() implements for which operations this type is valid, and how the values are manipulated.
- The operations for addition and subtraction are simply wrappers to the CVector3f '+' and '-' operator implementations.
- The only valid comparison operations are '==' and '!='. Arguably one could implement, say, '<' for CVector3f, and assume this was a length comparison.
-
Vector3fScale() implements a couple additional operation implementations
- Multiplication and Division are not valid operations between two CVector3f values, however, you can scale a CVector3f by the value of a float.
-
Vector3fToBoolConvert() contains the only viable conversion, which is from a CVector3f to a boolean.
- The converted value is 'true' if the value contains any non-zero component, 'false' if the value is (0, 0, 0).
This class is a plain old data (POD) structure, so in addition to registering the class implementation, the operations and conversions applicable to this type, individual members of this type (namely, the x, y, z members) are also accessible.
- The table is created and stored in the variable gVector3fTable.
- Each entry in the table specifies:
- The type of the POD member (in this case, all three are TYPE_float)
- The byte offset into the value, where the POD member can be accessed (0, 4, 8 bytes for floats x, y, z)
- Each member POD member is referenced by a given name (strings "x", "y", "z"), the hash of which is how they're added to the table.
- The TinScript syntax for referencing a POD member uses the ':' operator.
- e.g. Accessing the "position" member (type vector3f) of an object, the syntax is object.position
- Accessing the "y" POD member of that position uses the syntax: object.position:y
- Vector3fConfig() is the entry point to configuring our new type.
- It is called both to initialize the type (in this case, this is when the POD table is created), and to shut down the type (deallocating the POD table).
- On initialization, first the POD table is created and registered.
- Next, operations for OP_Add, OP_Sub, OP_CompareEqual and OP_CompareNotEqual are registered with their implementation in Vector3fOPOverrides
- OP_BooleanAnd and OP_BooleanOr are registered as well, but since we've provided a conversion to TYPE_bool, we can register the BooleanBinaryOp() function to handle the implementation, knowing that the first thing it will do, if given TYPE_vector3f values, is to convert them to TYPE_bool.
- Finally, we register this Vector3fToBoolConvert() function.
So why not rely on class registration - why bother with the overhead registering CVector3f as a type?
Actually, to illustrate the comparison between class and type registration, I've registered CVector3f as *both*.
- As a class:
- You have access to class methods such as:
- CVector3f::Set(x, y, z); // to initialize the x,y,z members
- CVector3f::Normalize(); // alters the object directly, normalizing the vector
- You have access to the members through object.x, object.y, object.z
- You have access to class methods such as:
- As a type:
- You have to use global registered functions:
- Initialize by assigning the POD members individually:
- vector3f v; v:x = <value>; v:y = <value>; v:z = <value>;
- You can also initialize from a string
- vector3f v = "1.0f, 2.0f, 3.0f";
- Our equivalent of normalize then uses the registered function: CVector3f V3fNormalized(CVector3f v);
- vector3f v_norm = V3fNormalized(v);
- Initialize by assigning the POD members individually:
- You have to use global registered functions:
- The biggest disadvantage as a type then is
- You have to implement global functions to wrap each of the methods implemented in CVector3f.
- The biggest three advantages are:
- You don't have to jump back and forth between script and code to perform operations - the type is now part of the language, and the virtual machine can manipulate the type directly.
- As a type, you've now allowed any other function or method that takes a CVector3f as an argument or return value, to be registered directly.
- As a type, you don't have to constantly create and destroy instances of the class CVector3f.
- Instead of:
object v = create CVector3f();
v.Set(1.0f, 2.0f, 3.0f);
// ... use 'v' for whatever...
destroy v; - You can use (with no cleanup afterwards):
vector3f v = "1.0f, 2.0f, 3.0f";
// ... use 'v' for whatever...
- Instead of: