VR-Forces 5.0.3 Developer's Guide
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
Starting and Processing C++ and Scripted Tasks

Table of Contents

Introduction

VR-Forces has two forms of tasks, two means of specifying task identities and parameters, and two ways of programming task behavior. We will call these the C++ and scripted forms. This section describes how to write C++ code to issue either C++ or scripted task messages to start a task, and how to write C++ code in a C++ task controller to process C++ or scripted task messages.

Tasks in the C++ form are identified by different C++ classes (subclasses of DtSimTask). These classes include members which define the parameters of the task. C++ task behavior is programmed in a controller component that registers to handle messages containing a specific task class.

The scripted task form is an alternative mechanism for both specifying and programming the tasks. Scripted tasks all use the DtScriptedTaskTask class. The task identity is defined in the myScriptId member. The task parameters are represented as a list of name-value pairs. Scripted task behavior can be programmed in a C++ controller or in a script environment.

Because behavior for tasks identified in a Scripted Task class can be programmed in either C++ or Lua (a scripting language), there is some ambiguity in terminology when referring to "scripted tasks." The term "task" could refer to the identifying task message or the code that implements the behavior, and the two can use different forms. In this document we will try to avoid confusion by specifying task messages or task behavior code when talking about a particular task form.

The relationship between the elements described above is illustrated in the following figure:

cppandscriptedtasks.png
C++ Task and Scripted Task Processing

This document does not cover how to start or process tasks in Lua scripts; see instead the Lua API documentation. Briefly, either form of task is started by calling vrf:startSubtask() (or a similar function), and parameters of either form of task are handled by accessing entries in the taskParameters table.

Starting C++ Tasks

Creating a C++ Task Message

C++ tasks are started from within C++ code by creating a task class of the appropriate type, filling in task parameters using class member functions, and sending the task as a message. The following is an example of creating a move-to task:

DtSimObjectReference myControlPoint...
DtMoveToTask moveToTask;
moveToTask.setControlPoint(myControlPoint->uuid());
moveToTask.setSubtask(true);

The final line makes this task a subtask, which is usually appropriate when the code that is issuing the task is in a task controller. If this is to be a top-level task, then the subtask member would be set false.

Sending a Task Message

After the task is created, it is sent to the simulation object as a message. The following are several common ways to send the task message for different situations.

Sending a Task Message as a Task From the Front End

If the VR-Forces front end is starting the task, it will typically use the remote control interface. In a task dialog derived from DtBaseTaskSetDialogInterface, a wrapper function can be used to send the task like this:

...
sendTaskRequest(task);

See the addSet example for similar code with a set command.

Sending a Task Message as a Subtask

Single and multi-task controllers have a function sendSubtask(DtSimTask& task). This starts a subtask on the same simulation object. This function doesn't send a message, but creates a task message and directly calls taskManager->executeTask with the task message.

DtMoveToTask someTask;
...
sendSubtask(someTask);

Sending a Task Message as a Task Using SendTask

The multiTaskController has a sendTask(DtSimTask& task,... ) function. This function sends a task message to a given simulation object. The function returns a subtask ID with which to track the task. The function declaration is as follows:

int sendTask(DtSimTask& task, const DtUUID& receiver, int taskId,
bool actAsSubtask = true, bool useRadio = false);
// In versions 4.3.1 and before:
// int sendTask(DtSimTask& task, const DtString& receiver, int taskId,
// bool actAsSubtask = true, bool useRadio = false);

It might be appropriate to use this function to send tasks from a unit to its subordinates, or from a simulation object to the passengers embarked on it.

Sending a Task Message Using the Communications Interface

Unlike the multiTaskController, the singleTaskController does not have a sendTask() function. However, tasks can be sent from single task controllers or other C++ code modules by replicating the necessary setup steps. The setup involves creating a DtTaskMessage to wrap the task. For example:

DtUUID receivingEntity;
// In versions 4.3.1 and before:
// DtString receivingEntity; // name
// Fill in task parameters
...
DtTaskMessage taskMessage;
taskMessage.setReceiver(receivingEntity);
taskMessage.setTask((DtSimTask*)&task);
taskMessage.setIndependentlyTasked(false);
entity()->communicationInterface()->sendSimInternalMessage(&taskMessage);
// If using the simulation object radio:
// entity()->communicationInterface()->defaultRadio()->sendMessage(&taskMessage);

Note that several existing controllers written years ago have members identifying the simulation object radio, and so send tasks using a radio interface like this:

radio()->sendToNet(&taskMessage);

This function simply calls the communication interface sendSimInternalMessage function.

Handling C++ Task Messages

A C++ controller component that implements task behavior must include code to do the following:

Examples of registering and defining a callback function for tasks are provided in the addTask example.

To access the parameter values, the processing function gets the appropriate task class from the message and uses its access member functions. For example, the target selection controller gets the auto-select-weapon flag and the target simulation object ID this way:

{
DtTaskMessage* task = dynamic_cast<DtTaskMessage*>(msg);
DtFireAtTargetTask* fireTask = dynamic_cast<DtFireAtTargetTask*>(task->task());
...
if (fireTask)
{
bool autoSelectWeapon = fireTask->autoSelectWeapon();
...

Starting Tasks That Use Scripted Task Messages

Creating a Scripted Task Message

As with C++ tasks, tasks that use scripted task messages are started by creating a task structure, specifying parameter values, and sending the task. The task structure is sent as a message exactly the same way a C++ task message is sent. For scripted tasks, however, the task class is always DtScriptedTaskTask, and parameter values are specified by creating and adding DtRw variables to the task. Here is an example of specifying several types of parameters in a task:

DtSimObjectReference targetEntity;
...
// Set up the task object
DtScriptedTaskTask task;
task.init();
task.setScriptId("test_task"); // This string matches the name of the task.
// A selection parameter
task.setValue("p1_selection", 2); // "p1_selection" is the name of the parameter
// An int parameter
task.setValue("p2_number_of_rounds", 2);
// A real parameter
task.setValue("p2_heading", 3.14);
// A weapon name parameter
task.setValue("p4_weapon", "120mm Gun");
// An entity type parameter
DtEntityType tmpType(1, 3, 225, 1, 1, 0, 0);
task.setValue("p5_entType", tmpType);
// A simulation object ("targetEntity")
task.setValue("p6_entity", targetEntity->uuid());

A complete list of the parameter types that are possible for the overloaded setValue function can be found in vrfTasks/scriptedTaskTask.h.

For parameter types that are not included in the overloaded versions of setValue, an RW variable must be created explicitly. For example:

// An offset vector parameter
DtRwOffsetVector* p8RwVar = new DtRwOffsetVector("p8_offset");
p8RwVar->setX(0.3);
p8RwVar->setY(0.1);
p8RwVar->setZ(0);
task.variables().addVariable(p8RwVar);

The task parameters above have names "p1_selection," "p2_time," and so on. The type names embedded in these names reflect the parameter types that can be selected in the scripted task dialog manager in the front end. These parameters would be accessed in a Lua script as taskParameters.p1_selection, taskParameters.p2_time, and so on.

Sending the Task

Scripted task messages are sent exactly the same way C++ task messages are sent.

Handling Scripted Task Messages

As with handling C++ task messages, a controller that handles scripted task messages must register to handle the message and define a callback function.

Retrieving Parameter Values

When a C++ controller receives a task message containing a scripted task, the task parameters cannot simply be taken from the task class members. Instead, the list of name-value pairs in the scripted task has to be searched for the expected variable names. The variables() function provides the variable bindings in the task structure, and looking up a variable in these bindings returns a pointer to a DtReaderWriter that holds the parameter value. The following is an example of accessing parameter values in a message processing function:

{
if (msg)
{
DtTaskMessage * tm = dynamic_cast <DtTaskMessage *> (msg);
DtScriptedTaskTask * turnMoveTask = dynamic_cast <DtScriptedTaskTask *> (tm->task());
// Since the controller registered this callback function for a specific
// task type, we assume that the above dynamic cast succeeds, and that
// turnMoveTask->type() has the correct value here.
// Integer or selection parameter
DtRwInt* rwTurnType = dynamic_cast<DtRwInt*>(turnMoveTask->variables().
findVariableBinding("turnType"));
int turnType = 0;
if (rwTurnType)
{
turnType = rwTurnType->value();
}
// Real parameter
DtRwReal* finalHeading = dynamic_cast<DtRwReal*>(turnMoveTask->variables().
findVariableBinding("finalHeading"));
real finalHeading = 0.0;
if (rwFinalHeading)
{
finalHeading = rwFinalHeading->value();
}
// Sim object parameter
// (This is not part of the turnMove controller, but added as an example)
DtRwUUID* uuid = findVariableBindingWithDefault<DtRwUUID>(
turnMoveTask->variables(), *defBindings, "targetEntity");
DtSimObjectReference targetObject;
if (rwEntity)
{
targetObject = simulationServices()->simObjectManager()->lookup(*uuid);
}

Using Default Parameter Values

When scripted tasks are created in the scripted task manager in the front-end, default values can be supplied to the parameters. To access these default values, the script is looked up in the scripted task manager, which returns another set of variable bindings containing default values. A utility function in propertyUtils.h makes the code concise. The following is an example of using default values:

{
if (msg)
{
DtTaskMessage * tm = static_cast <DtTaskMessage *> (msg);
DtScriptedTaskTask * turnMoveTask = dynamic_cast <DtScriptedTaskTask *> (tm->task());
simulationServices()->scriptedTaskManager()->lookupScriptById(turnMoveTask->scriptId().c_str(), md);
DtRwVariableBindings* defaultBindings = md.defaultVariableBindings();
DtRwInt* rwTurnType = findVariableBindingWithDefault<DtRwInt>(
turnMoveTask->variables(), *defaultBindings, "turnType");
if (rwTurnType)
{
turnType = rwTurnType->value();
}
// For versions 4.3.1 and before, the utility function above is not
// available, so findVariableBinding must be used twice:
//DtRwInt* rwTurnType = dynamic_cast<DtRwInt*>(turnMoveTask->variables().
// findVariableBinding("turnType"));
//int turnType = 0;
//if (rwTurnType)
//{
// turnType = rwTurnType->value();
//}
//else
//{
// rwTurnType = dynamic_cast<DtRwInt*>(defaultBindings->
// findVariableBinding("turnType"));
// if (rwTurnType)
// {
// turnType = rwTurnType->value();
// }
//}
...
delete defaultBindings; // defaultVariableBindings() provides a new copy,
// so it must be deleted after use.

Document ID: Generated on Thu Jun 1 17:58:13 EDT 2023 from SVN revision 255404
Copyright © 2005-2021 MAK Technologies. All Rights Reserved (www.mak.com)