Torque/ConsoleTypes

From TDN

What is a Console Type?

Console Types in a most general sense are a way for the console to pass data from the scripting language to C++ code through use of get and set data functions. The get function assigned to a specific console type, for example, TypeS32 will get the value of the specified S32 in the engine and translate it into a string of characters and return it. This allows the scripting language to be able to make use of this value. For example, the value 33 would be returned to the console as "33". Alternatively, the set function for a console type will take a string of characters from the console and translate it into the specified type so that it may be accessed by code in the engine. Again, for a TypeS32 that is passed to the set function it would pass in "33" and the function would call dAtoi to make it's string representation equate to the integer value 33.


Implementing a Console Type

Keeping with our TypeS32 example, let's take a look at how TypeS32 is implemented in the engine.


First we must Define the type in a header file that may be included by any other source file that would like to know about this specific type. In this case, it is decfined in console/consoleTypes.h


consoleTypes.h

DefineConsoleType( TypeS32 )

As you can see, defining a console type is rather simple. DefineConsoleType is actually a macro that is expanded out by the preprocessor which handles all the nasty grunt work of making the type known throughout the engine.


After we have defined the type, we must Declare the console type in the source file, which will actually make the type register itself with the console at startup. In this case, we are declaring the type in console/consoleTypes.cc


consoleTypes.cc

ConsoleType( int, TypeS32, sizeof(S32) )

The ConsoleType macro takes three parameters, the first being the string representation of the name of the type. You'll notice there are no "" around the int in this macro. That is because the macro will automatically add the "" to it when the preprocessor expands it out. The type name specified by the first parameter does NOT have to be the actual data type name, it is simply used so that code may call getTypeName which will return a string description of the type which in this case would be "int".

The second parameter passed to the ConsoleType macro is the type name which is used throughout the engine to make reference to the console type. This will ALWAYS be the same as the typename you specified in the DefineConsoleType macro.

The last parameter passed to the ConsoleType macro is the size of the value to be stored. In this case it is sizeof(S32), this allows the console to know how much memory is needed to store a value of TypeS32 for access by the engine and scripting language.


After we have implemented the ConsoleType macro, we must now give the console the body of our Get and Set Data functions for this type. That is to say we must actually fill in the code that takes a S32 and converts it to a string for the scripting language, and also the code that does the reverse.

First we'll take a look at how a GetData function is implemented (once again, this one gets the data from the engine and makes it readable by the console.

The ConsoleGetType macro wraps a function body declaration much as the ConsoleMethod macro does. The actual function that this macro expands out to looks like the following.

const char * getDataTypetype(void *dptr, EnumTable *tbl, BitSet32 flag )

dptr is a void pointer to the data associated with this value. In our GetType, we will dereference this pointer after casting it from a void* to a S32* so that we have access to the S32 value associated with it.

tbl is a pointer to an EnumTable associated with this type. EnumTables are used for such things as the HorizSizing persistent field associated with GuiControls and they have specified values to choose from. In most cases you will not need to worry about this parameter, and a more indepth discussion about how to use it is, for the time being, outside the scope of this article.

flag is a BitSet32 which may be used for testing a bit on a given BitSet. This parameter is not put to use in the engine, though TypeFlag is implemented in consoleTypes.cc and as such, a more indepth explanation into how to use this parameter is outside the scope of this article.

Using the information passed to use by the console, we now know enough to convert this into a console usable string, let's see how it's done!

consoleTypes.cc

ConsoleGetType( TypeS32 )
{
   char* returnBuffer = Con::getReturnBuffer(256);
   dSprintf(returnBuffer, 256, "%d", *((S32 *) dptr) );
   return returnBuffer;
}

In this function as you can see we allocate a return buffer of size 256 in which we then convert our data to a string and return it for the consoles use.

Next we must implement our SetData function (once again, this take a string of characters and converts them to our typed value for use in the engine.)


The ConsoleSetType macro much like the ConsoleGetType macro wraps a function body declaration. The actual function that this macro expands out to looks like the following.

const char * setDataTypetype(void *dptr, S32 argc, const char **argv, EnumTable *tbl, BitSet32 flag)

dptr is a void pointer to the data associated with this value. In our SetType we will once again dereference this pointer after casting it from a void* to a S32* so that we can assign our specified value to it after converting it from a string of characters into a usable type.

argc is the number of parameters passed in from the console to this function.

argv is a pointer to an array of character pointers representing the parameters passed into the function.

tbl is a pointer to an EnumTable associated with this type. EnumTables are used for such things as the HorizSizing persistent field associated with GuiControls and they have specified values to choose from. In most cases you will not need to worry about this parameter, and a more indepth discussion about how to use it is, for the time being, outside the scope of this article.

flag is a BitSet32 which may be used for testing a bit on a given BitSet. This parameter is not put to use in the engine, though TypeFlag is implemented in consoleTypes.cc and as such, a more indepth explanation into how to use this parameter is outside the scope of this article.

Now that we know what we have to work with, let's see how it's done!

consoleTypes.cc

ConsoleSetType( TypeS32 )
{
   if(argc == 1)
      *((S32 *) dptr) = dAtoi(argv[0]);
   else
      Con::printf("(TypeS32) Cannot set multiple args to a single S32.");
}

Here you can see that since a S32 is just a single number, we have to check to make sure we have the proper number of arguments passed in. If you for example tried to se this to "32 32" the console would print a nice error message telling you that you did something wrong! After we've verified we have the correct number of arguments, we de-reference the data pointer and assign it our value passed into the function.

That's it, how simple, right?!

Reviewing

After all of that, let's take a quick look back at what all we must do to succesfully implement a console type.


Define The Type in your header file

  - DefineConsoleType( YourTypeName ) 

Declare The Type in your source file

  - ConsoleType( stringTypeName, YourTypeName, SizeOfType )

Implement the GetData function in your source file

  - ConsoleGetType( YourTypeName ) { return string represting the value }

Implement the SetData function in your source file

  - ConsoleSetType( YourTypeName ) { convert the string value into your engine data type and assign it to the data pointer }