Introduction to GameMonkey Script Part 2
Embedding GameMonkey
gmVariable ObjectThe gmVariable type is important in many areas when accessing the VM API. Instead of having to provide multiple API functions to handle every conceivable object type GM has provided the gmVariable. The gmVariable is used in everything from function parameters and return data to table data objects; this object holds the variable data and can be passed to and from the VM as it holds information about the type of data as well as the data itself. The GM machine will read off the variable type before deciding how to proceed with handling the data it contains so it is vitally important that you set the appropriate type when manipulating your variables. Let's have a look at how you can create and manipulate a new variable: int main() { gmMachine gm; // Allocate a string from the machine and set it as a variable gmVariable stringVar( gm.AllocStringObject("Hello, World") ); // Allocate a variable containing an int and a float // note it doesn't need to be created from the machine gmVariable intVar( 100 ); gmVariable floatVar( 1.5f ); // Create a variable with a newly created table gmVariable tableVar( gm.AllocTableObject() ); // Reset table var as an int, losing the table data it contained tableVar.SetInt( 200 ); // Variable copying intVar = floatVar; // Make 'null' stringVar.Nullify(); return 0; }
As mentioned previously, you need to allocate String and Table objects from the gmMachine object, but because floats and integers are native types, you are not required to perform any extra allocation to hold the data they contain. The variables created in the example don't actually do anything, but they are all ready to be pushed as a function parameter or set in a table. As you can see above, like in GMScript itself, you are free to assign variables, reset variables with new data or even completely nullify them. Sometimes you will be presented with a gmVariable from the machine; it may be come as a function parameter or as the result of a Get on a gmTableObject. In these situations you will need to know how to retrieve the data. If you look in the gmVariable.h header you will see the following code in the gmVariable class. gmType m_type; union { int m_int; float m_float; gmptr m_ref; } m_value; This should give you a hint in how to retrieve the data. Before you can do anything else you must check the m_type member. This holds the appropriate enumeration and will be either GM_NULL, GM_INT, GM_FLOAT, GM_STRING, GM_FUNCTION, GM_TABLE or an integer for your own types which increment from GM_USER. Once you have ascertained the type you can retrieve the value from the m_value union; in the case of integers and floats this is as simple as accessing m_int and m_float respectively, but what happens for other types? The answer should be obvious - you need to cast the m_ref pointer to your appropriate type. It should be noted, however, that there are no runtime checks to ensure you're casting to the correct type; it is for this precise reason that you should always check the type before you attempt to cast. In a dynamically typed environment such as GameMonkey Script there are no guarantees about the type of data you will receive, so you must place it upon yourself to check before you access the data in the variable. The following example will highlight how to check for the type and access the data accordingly. As an exercise, try experimenting by changing the value you place in the var gmVariable and examine the results you get back. using namespace std; int main() { gmMachine gm; // Try setting your own variable here gmVariable var( gm.AllocStringObject("Hello, World") ); switch (var.m_type) { case GM_NULL: cout << "Variable is NULL type" << endl; break; case GM_INT: cout << "Variable is INT type" << endl; cout << "Value:" << var.m_value.m_int << endl; break; case GM_FLOAT: cout << "Variable is FLOAT type" << endl; cout << "Value:" << var.m_value.m_float << endl; break; case GM_STRING: cout << "Variable is STRING type" << endl; cout << "Value:" << static_cast<gmStringObject *>(var.m_value.m_ref)->GetString() << endl; break; case GM_TABLE: cout << "Variable is TABLE type" << endl; cout << "Items:" << static_cast<gmTableObject *>(var.m_value.m_ref)->Count() << endl; break; case GM_FUNCTION: cout << "Variable is FUNCTION type" << endl; break; default: cout << "Variable is USER type" << endl; // retrieve native pointer from user object void *ptr = static_cast<gmUserObject *>(var.m_value.m_ref)->m_user; }; return 0; }
A final word of caution about gmVariables; numeric data can be stored with either the GM_INT or the GM_FLOAT type depending on whether it has a decimal or not. For example, imagine a script which uses a loop to count from 0 to 10 in increments of 0.5f. The variable will initially hold data of int type (zero is an int), and will alternate between being a float and an int on every second iteration. Because of this, it is important that any function that expects numeric float data performs a check to see if the variable is either a GM_INT or a GM_FLOAT. |