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.

Table of Contents

CVector3f

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.

Type Registration

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

Operations and Conversions

  • 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).

POD Members

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

Configuration

  • 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.

Type vs Class Registration

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
  • 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);
  • 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...


              
⚠️ **GitHub.com Fallback** ⚠️