Console

Overview

The console module, rooted in engine/console/console.h, is a combined compiler and interpreter runtime that serves as the foundation for Torque applications. All GUIs, game objects, interfaces, and game logic are handled through the console. The language itself is syntactically similar to a typeless C++, with some additional features that allow for easier mod development. Console scripts can be loaded via the exec() console command from the console window (brought up using the ~ key) or they can be loaded automatically from a mod via that mod's main.cs.

Console Language Reference

The Torque console language scanner and parser were built using the tools lex and yacc. The scan and grammar files are engine/console/scan.l and engine/console/gram.y respectively. The grammar is shown in a somewhat more understandable way in the attached document.

Functions

Console functions can be declared in either console scripts or in the C++ game/engine code.

// A simple script function declaration.
function helloWorld ()
{
    echo("Hello World!");
}

// Let's call it!
helloWorld();

// Or, for a method on a class:
function SimObject::helloWorld(%this)
{
    echo("Hello World!");
    echo("Called on object: " @ %this);
}

// And call it...
$object = new SimObject();
$object.helloWorld();

Note:
Unlike in C++, the "this" variable is not implicit in a script's method declaration. Instead, the first parameter to a method is always the object whose method is being called. It is traditional but not required to call this parameter this; a common variant is db, for methods of datablocks.
To declare console functions in the game or engine code, the ConsoleFunction macro (declared in engine/console/console.h) should be used:

ConsoleFunction(helloWorld, void, 1, 1, "A simple test function.")
{
    Con::printf("Hello World!");
}

A rundown of the arguments to the ConsoleFunction macro:

  • First is the name of the function, followed by the type.
  • Second, come the minimum and maximum number of allowed arguments. The function name is "argument 0", so 1 is the minimum argument count allowed. Passing a 0 for max arguments will allow any number of arguments to the function.
  • Finally, the usage string. This serves a dual purpose. See this page for a more in-depth exploration of how the string can be used to autogenerate Doxygen compatible documentation from within your program. However, the string mostly exists to provide as feedback to users if they make a console function call with the wrong number of arguments.

Methods for classes can also be declared in C++, using the ConsoleMethod macro:

ConsoleMethod(SimObject, helloWorld, void, 2, 2, "A somewhat more complex test method.")
{
    Con::printf("Hello World!");
    Con::printf("Called on object: %s", argv[1]);
    Con::printf("Also called on object: %s", object->getName());
}

This defines a method callable on any instance of a SimObject the console has access to.

Note:
Three useful parameters are provided to the code in the body of a ConsoleFunction or ConsoleMethod:
  • int argc, indicating how many arguments were passed.
  • const char* argv[], an array of strings corresponding to the arguments.
  • object, which is present only in the case of a ConsoleMethod, is a pointer to the object on which the method is being called. It is automatically cast and validated, so you don't need to cast it yourself (this was the case in older versions of Torque).

Classes, Objects and Namespaces

Declaring Console Classes

Objects in the scripting language are simply instances of C++ classes deriving from SimObject, declared in the game engine and processed with a special set of macros (declared in engine/console/consoleObject.h). The DECLARE_CONOBJECT(class_name) macro is placed inside the class definition and the IMPLEMENT_CONOBJECT (class_name) macro is placed in a linked source file. IMPLEMENT_CONOBJECT has several versions, depending on the type of class:
  • IMPLEMENT_CONOBJECT() A simple console object - no special network attributes.

  • IMPLEMENT_CO_NETOBJECT_V1() A ghostable network object class. Any object that will be ghosted from server to client needs to be declared as a NETOBJECT. See the section on the network layer for more details about NetObjects.

  • IMPLEMENT_CO_DATABLOCK_V1() A datablock class. Datablocks have special properties for being transmitted over the network.

Note:
There are 3 more IMPLEMENT_CONOBJECTs that deal specifically with network events. They are not covered here.
Every class declared as a Console class MUST declare a Parent typedef in its private member section referencing its parent class. This allows the console to properly determine the console object class hierarchy for method dispatch in the console.

// A very simple SimObject sample.
class SampleObject : public SimObject
{
    typedef SimObject Parent;
public:
    DECLARE_CONOBJECT(SampleObject);
    S32 someVariable; // signed integer variable
};
IMPLEMENT_CONOBJECT(SampleObject);

You can instantiate and manipulate this object in script like this:

function foo()
{
    %obj = new SampleObject(MySampleObject);
    echo(%obj.getName());
}

Adding Class Member Fields

Data members of C++ classes can be accessed from within the scripting language.

When the game starts, the console calls AbstractClassRep::initialize(), which assigns network IDs to classes, links class hierarchies, and initializes field data for each class. To do this, each class with accessible data members declares a static member function called initPersistFields(). This function calls the addField static member function for each data member of the class:

void SampleObject::initPersistFields()
{
    Parent::initPersistFields();  // adds the parent class's fields as well.
    addField("someVariable", TypeS32, Offset(someVariable, SampleObject));
}

The type must be properly specified, of course. The Offset macro is used to determine the relative address of the data member in the class. Once this is defined, scripts can use the member field of the object directly:

function bar()
{
    %obj = new SampleObject(MySampleObject);
    %obj.someVariable = 100;
    echo(%obj.someVariable);
}

Field types are declared in engine/console/consoleTypes.h. New console data types can be added by adding a type to consoleTypes.h and calling Con::registerType(typeId, typeSize, getDataFunc, setDataFunc). See engine/console/consoleTypes.cc for examples of how types are defined.

Dynamically Defined Fields

Member fields of objects can also be defined from within the script itself. For example:

function foo()
{
    %object = new SampleObject();
    %object.scriptVariable = "Hello World!";
    echo(%object.scriptVariable);
}

Note:
Fields added in this way are only set and present on the instance on which they were set.

Namespaces

Namespaces (engine/console/consoleInternal.h) are collections of class member functions. Every SimObject belongs to exactly one namespace. By default, an object belongs to the namespace that corresponds to its class - so an instance of GameBase will have the GameBase namespace. Each namespace has a parent, so methods can invoke parent class methods by calling Parent::function().

New namespaces (not class-based) can be added in the engine via the Con::linkNamespaces() function. Assigning the mNameSpace field of SimObject then assigns the new namespace to an object. For example, in GuiControl::onAdd(), if a control has a name its name is used as its namespace. This allows a named GUI control to have special behaviors.

The ScriptObject class (defined in engine/console/scriptObject.cc) allows for the creation of "classes" within the scripting language:

new ScriptObject(MyObject) {
    class = Bar;
    superClass = Foo;
};

function Bar::doSomething(%this)
{
    echo("Hi!");
}

MyObject.doSomething();
> Hi!

function Foo::doSomething(%this)
{
    echo("Hi! Foo");
}

function Bar::go(%this)
{
    %this.doSomething();
    Parent::doSomething(%this);
}

MyObject.go();
> Hi!
> Hi! Foo

Note:
Every SimObject in the system can be addressed either by name or by ID. So in the example above, MyObject.go() searches the object dictionary for an object named MyObject and calls the go() method on that object.

Packages

Packages are collections of functions that can be enabled and disabled at runtime. Package functions can override (redefine) the behavior of existing functions in the global state or in packages that have been activated earlier. Prior versions of a function can then be accessed using "Parent". For example:

function foo()
{
    echo("foo!");
}

package SamplePackage
{

function foo()
{
    echo("Haha!");
    Parent::foo();
}

}

% foo();
foo!
% ActivatePackage(SamplePackage);
% foo();
Haha!
foo!

Packages are useful for creating mods to games or for implementing specific game modes.

Variables

The console language supports global variables and local (function scoped) variables. Global variables are specified by a preceding $, and local variables by a % sign. Example:

$someGlobal = "This is some global.";

function foo(%local1, %local2)
{
    %local3 = $someGlobal;
}

function bar(%local)
{
    // You can also create a new global from within a function!
    $alottaGlobal = %local @ " is.";
}

Arrays

The console language supports associative single- and multi-dimensional arrays. Arrays actually construct new variables with the names concatenated - so for example $array[10] is the same as $array10, while $array[3, 4] is the same as $array3_4. Strings can be used as array indexes as well: $array["foo"] = 100;. Array dimensions are separated with commas inside the brackets - $array[1, 0] = 10;

Special String Operators

There are several special operators for strings in the scripting language:
  • $= Case insensitive string comparison. True if strings are equal.
  • !$= Negative case insensitive string comparison. True if strings are not equal
  • @ String concatenation operator: "Hello " @ "World!" == "Hello World!"
  • TAB String concatenation with a tab. "Hello" TAB "World!" == "Hello\tWorld!"
  • NL String concatenation with a newline. "Hello" NL "World!" == "Hello\nWorld!"
  • SPC String concatenation with a space. "Hello" SPC "World!" == "Hello World!"

Compiler

Scripts are executed in a two step process: First the script is compiled into a tokenized instruction stream, then the instruction stream is processed using the compiled evaluator.

Debugger

The console supports remote debugging via another instance of Torque. In the game instance to be debugged, debugger port and password must be set using the dbgSetParameters(port, password); Then, in the instance to be used as the debugger, the GUIs and scripts in common/debugger/ must be loaded.

See also:
TelnetDebugger for more details on the debugger.

Interfacing with C++ Code

The C++ game and engine code can be called from the scripts as described above, and the game code can also call into script using the console execute and evaluate functions:

// simple execute of a console function using argv array:
const char *execute(S32 argc, const char* argv[]);

// simple execute of a console function, without stuffing an array
const char *executef(S32 argc, ...);
   
// execution of a method on a SimObject using argv array:
// first param is func name, second param MUST be empty
// also, MUST have at least those two params
const char *execute(SimObject *, S32 argc, const char *argv[]);

// execution of a method on a SimObject without stuffing an array
// first param is funcName, remaining params are args
const char *executef(SimObject *, S32 argc, ...); 

// evaluation of an arbitrary console command script:
const char *evaluate(const char* string, bool echo, const char *fileName);

// evaluation of a formatted (ala printf) command string:
const char *evaluatef(const char* string, ...);

Examples:

SimObject *mySimObject = new SimObject;

Con::executef(mySimObject, 4, "doSomething", Con::getIntArg(20), "Bye", "Hi");
Con::evaluatef("mySimObject.doSomething(%d,\"%s\",\"%s\");", 20, "Bye", "Hi");

const char *argv[5];
argv[0] = "doSomething";
argv[1] = NULL;
argv[2] = Con::getIntArg(20);
argv[3] = "Bye";
argv[4] = "Hi";

Con::execute(mySimObject, 5, argv);

The functions Con::getIntArg, Con::getFloatArg and Con::getArgBuffer(size) are used to allocate on the console stack string variables that will be passed into the next console function called. This allows the console to avoid copying some data.