VR-Link API Documentation for HLA 4
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
Test Interaction Example

Table of Contents

The testInter example is an HLA-specific example that shows how to add a new interaction class to a FOM.

We need to extend the VR-Link top-level API by adding a new kind of DtInteraction. Then we create mappings between the new FOM class and the new DtInteraction subclass.

This example adds an interaction class called Test to the VrlExtend.fed FED File. It has two parameters:

How to Run the Example

  1. Launch two testInter applications from your VR-Link install's bin64 directory using the same HLA protocol.
  2. Once running, you can observe the Test Interactions sent between the two applications. The interactions will include the two new parameters, Number and Vector.

How to Add a New Interaction Class

To add a new interaction class:

  1. Create a new subclass of DtInteraction to represent the new kind of interaction. (See testInter.h and testInter.cxx.)
  2. Create TestEncoder and TestDecoder classes, subclasses of DtInteractionEncoder and DtInteractionDecoder respectively. These classes contain encoding and decoding functions for the parameters of the Test interaction. (See testDec.h, testDec.cxx, testEnc.h and testEnc.cxx.)

Subclassing the DtInteraction Class

The files testInter.h and testInter.cxx contain definitions for a new class called TestInteraction, and its member functions. Like all interaction classes in VR-Link, it is derived from DtInteractionWithEncDec, rather than directly from DtInteraction. DtInteraction is rather general, and allows for a wide variety of subclass implementations, including those that do not involve our concept of encoders and decoders. DtInteractionWithEncDec is set up to deal with encoders and decoders, and we will be taking advantage of this.

TestInteraction includes inspector and mutator functions to access the values of the Number and Parameter parameters. We represent them in the same way as in the FOM, but this is not strictly necessary. We could perform a non-trivial conversion in FOM mapping code instead.

The TestInteraction class hard-codes its own FOM mapping information, rather than relying on the default behavior, which is to obtain it from the FOM Mapper. We do this here, because it make things simpler, by saving us the step of configuring the FOM Mapper with mappings for our new class. But this choice means that it would be more difficult to change the way this kind of interaction is represented in the FOM.

This choice manifests itself in the fact that we override the virtual functions interactionClassToUse(), createEncoder() and createDecoder(). The base class implementation of interactionClassToUse() asks the FOM Mapper for the name of a FOM class to use. But TestInteraction's implementation just returns the class name Test. The base versions of createEncoder() and createDecoder() ask the FOM Mapper for instances of the kind of encoder and decoder that have been registered with it for use with the Test interaction class. But TestInteraction's implementations of these functions just return a new'ed TestEncoder and TestDecoder instance respectively.

In addition, within TestInteraction::addCallback(), we inform the DtExerciseConn's interaction factory that it should create an instance of the TestInteraction class to represent incoming interactions of FOM class Test. This is achieved using DtInteractionFactory's addCreator() member function.

Notice that the TestInteraction has decided that it will use instances of the classes TestEncoder and TestDecoder to map between its representation of its parameters, and the FOM representation.

Create Test Interaction Encoder

TestEncoder, defined in testEnc.cxx, is derived from DtInteractionEncoder. Macros help out in the declaration and definition of the encoding functions for individual parameters. (Again, encoding functions are provided as static members of the TestEncoder class.)

The "simple" implementation provided by the macros for an encoding function is to obtain a value from a particular interaction class inspector function, cast it to the appropriate "Net" type, which performs byte swapping if necessary, and adds the value to an RTI::ParameterHandleValuePairSet for sending.

The TestEncoder constructor just adds its encoding functions to the base class's table using addEncoder().

Create Test Interaction Decoder

TestDecoder, defined in testDec.cxx, is derived from DtInteractionDecoder. Macros help out in the declaration and definition of the decoding functions for individual parameters. The "simple" implementation provided by the macros for a decoding function is to get a pointer to the parameter data from an RTI::ParameterHandleValuePairSet, cast it to a pointer to an appropriate "Net" type, then pass it to one of the interaction class's mutator functions. The implicit cast from the "Net" type to the type expected by the mutator functions will perform byte swapping if necessary.

The TestDecoder constructor just adds its decoding functions to the base class's table using addDecoder().

Note
While decoding functions are implemented as static member functions of the TestDecoder class, this was not strictly necessary - we could have written them as ordinary global function instead. But by implementing them as static members of the class, we can take advantage of some VR-Link macros.

Main Application

Once we build the TestInteraction class, and outfit it with a TestEncoder and TestDecoder, we can use it in an application just like any other DtInteraction subclass. In testInterMain.cxx, we create a TestInteraction, fill it out, and send it. We also register a callback on incoming TestInteractions, and print their contents using the virtual printDataToStream() function.

After you build the testInter example, run two copies of it. You should see them communicating with each other. Each should print TestInteractions received from the other.

TestInteraction Example Code

Main Application

/****************************************************************************
* Copyright (c) 1992-2025 MAK Technologies, Inc
* All rights reserved.
****************************************************************************/
#include "testInter.h"
#include <iostream>
// Callback function to be called when a TestInteraction is received.
void testCb(TestInteraction* inter, void*)
{
std::cout << "Received Test Interaction!\n";
inter->print();
std::cout << std::endl;
}
int main( int argc, char* argv[] )
{
// Used for error handling
DtINIT_MINIDUMPER( "InterTest" );
DtExerciseConn conn("example-vrlExtend", "TestInter", new DtEmptyFomMapper() );
DtClock* clock = conn.clock();
// Register the callback on incoming TestInteractions.
TestInteraction::addCallback(&conn, testCb, NULL);
while (1)
{
clock->setSimTime(clock->absRealTime());
conn.drainInput();
if (input.keybrdTick() == -1) break;
// Send a TestInteraction.
inter.setNumber(10);
inter.setVector(DtVector(1.0, 2.0, 3.0));
conn.sendStamped(inter);
DtSleep(1.0);
}
return 0;
}

TestEncoder

TestEncoder Header File

/*********************************************************************
** Copyright (c) 1992-2025 MAK Technologies, Inc
** All rights reserved.
*********************************************************************/
#pragma once
#if DtHLA
#define DtDECLARE_TEST_ATTR_CHECKER(attrName) \
DtDECLARE_ATTR_CHECKER(TestStateRepository, attrName)
#define DtDECLARE_TEST_ATTR_ENCODER(attrName) \
DtDECLARE_ATTR_ENCODER(TestStateRepository, attrName)
#define DtDEFINE_SIMPLE_TEST_ATTR_ENCODER( \
attrName, netType, inspector) \
DtDEFINE_SIMPLE_ATTR_ENCODER(TestEncoder, \
TestStateRepository, attrName, netType, inspector)
#define DtDEFINE_SIMPLE_TEST_ATTR_CHECKER( \
attrName, inspector) \
DtDEFINE_SIMPLE_ATTR_CHECKER(TestEncoder, \
TestStateRepository, \
attrName, inspector)
{
public:
DtExerciseConn* exConn,
DtObjClassDesc* classDesc);
virtual ~TestEncoder();
protected:
};
#endif

TestEncoder Source File

/*********************************************************************
** Copyright (c) 1992-2025 MAK Technologies, Inc
** All rights reserved.
*********************************************************************/
#if DtHLA
#include "testEnc.h"
#include "testSR.h"
DtExerciseConn* exConn,
DtObjClassDesc* classDesc) :
DtHlaStateEncoder(exConn, classDesc)
{
// Register the individual encoding and checking functions for Test's
// attributes with the object. The encoding functions have names like
// encodeNumber, while checking functions have names like needNumber. The
// macros expands to look like this:
// addEncoder("Number", (DtAttributeEncoder) encodeNumber);
// addChecker("Number", (DtAttributeChecker) needNumber);
}
{
}
// Encoding functions
Number, DtNetU32, number);
Vector, DtNet64Vector, vector);
// Checking functions
Number, number);
Vector, vector);
#endif

TestDecoder

TestDecoder Header File

/*********************************************************************
** Copyright (c) 1992-2025 MAK Technologies, Inc
** All rights reserved.
*********************************************************************/
#if DtHLA
#pragma once
#define DtDECLARE_TEST_ATTR_DECODER(attrName) \
DtDECLARE_ATTR_DECODER(TestStateRepository, attrName)
#define DtDEFINE_SIMPLE_TEST_ATTR_DECODER( \
attrName, netType, inspector) \
DtDEFINE_SIMPLE_TEST_ATTR_DECODER_WITH_CAST( \
attrName, netType, inspector, (netType))
#define DtDEFINE_SIMPLE_TEST_ATTR_DECODER_WITH_CAST( \
attrName, netType, mutator, castExpr) \
DtDEFINE_SIMPLE_ATTR_DECODER_WITH_CAST( \
TestDecoder, TestStateRepository, \
attrName, netType, mutator, castExpr)
{
public:
DtExerciseConn* exConn,
DtObjClassDesc* classDesc);
virtual ~TestDecoder();
protected:
};
#endif

TestDecoder Source File

/*********************************************************************
** Copyright (c) 1992-2025 MAK Technologies, Inc
** All rights reserved.
*********************************************************************/
#if DtHLA
#include "testDec.h"
#include "testSR.h"
#include <vlutil/vlPrint.h>
// Default constructor
DtExerciseConn* exConn,
DtObjClassDesc* classDesc) :
DtHlaStateDecoder(exConn, classDesc)
{
// Register the individual decoding functions for Test's attributes with
// the object. The decoding functions have names like decodeNumber and
// decodeVector. The macro expands to look like this:
// addDecoder("Number", (DtAttributeDecoder) decodeNumber);
}
{
}
// Decoding functions
// The DtDEFINE_SIMPLE_ATTR_DECODER macro can be used to define a "simple"
// decoding function. Expanded, a decoding function looks like this:
//
// void TestDecoder::decodeNumber(TestStateRepository* stateRep,
// const RTI::AttributeHandleValuePairSet& attrs,
// int index)
// {
// RTI::ULong length = 0;
// // Get value from RTI representation into netVal.
// DtNetInt32* netVal = (DtNetInt32*) attrs.getValuePointer(index, length);
// // Check for size mismatch
// if (length > sizeof(DtNetInt32))
// {
// DtWarn("Size of value decoded for attribute %s (%d)\n",
// "Number", length);
// DtWarn(" is larger than size of %s (%d)\n",
// "DtNetInt32", sizeof(DtNetInt32));
// }
// // Pass the value to the mutator function, assuming the "net" type can
// // be implicitly cast to the native type expected by the mutator.
// stateRep->setNumber(*netVal);
// }
#endif

TestInteraction Class

TestInteraction Header File

/*********************************************************************
** Copyright (c) 1992-2025 MAK Technologies, Inc
** All rights reserved.
*********************************************************************/
#ifndef TestInteraction_H_
#define TestInteraction_H_
#if DtHLA
typedef void (*TestInteractionCb)(TestInteraction* inter, void* usr);
{
public:
virtual ~TestInteraction();
virtual const char* name() const;
virtual void printDataToStream(std::ostream& stream) const;
// **************************************************************
// Inspector and mutator functions for the interaction class' data.
// These will look different for each DtInteraction subclass.
// None are required by VR-Link, but the class will not be
// particularly useful without a way to get data in and out in a
// typesafe manner.
// **************************************************************
virtual void setNumber(int num);
virtual int number() const;
virtual void setVector(const DtVector& vec);
virtual const DtVector& vector() const;
public:
static DtInteraction* create();
static void addCallback(DtExerciseConn* conn,
TestInteractionCb cb, void* usr);
static void removeCallback(DtExerciseConn* conn,
TestInteractionCb cb, void* usr);
protected:
virtual const char* interactionClassToUse(DtExerciseConn* exConn) const;
protected:
int myNum;
};
#endif
#endif

TestInteraction Source File

/*********************************************************************
** Copyright (c) 1992-2025 MAK Technologies, Inc
** All rights reserved.
*********************************************************************/
#if DtHLA
#include "testInter.h"
// Header files for encoder and decoder.
#include "testEnc.h"
#include "testDec.h"
#include <iostream>
// Constructor - just initialize the base class, and local data holders.
myNum(0),
myVec()
{
}
// Destructor - nothing to do.
{
}
// Copy constructor
{
myNum = orig.number();
myVec = orig.vector();
}
// Assignment operator
{
// Guard against assignment to yourself.
if (this == &orig)
{
return *this;
}
myNum = orig.number();
myVec = orig.vector();
return *this;
}
const char* TestInteraction::name() const
{
// Return the name of this C++ class.
return "TestInteraction";
}
void TestInteraction::printDataToStream(std::ostream& stream) const
{
std::cout << "Number: " << number() << '\n';
// DtVector has a string() member function to get a printable string.
// Since it returns a DtString, we need to force it to const char* with a
// cast.
std::cout << "Vector: " << vector().string() << std::endl;
}
// Accessor functions are typically pretty straightforward.
{
myNum = num;
}
{
return myNum;
}
{
myVec = vec;
}
{
return myVec;
}
{
return new TestInteraction();
}
TestInteractionCb cb, void* usr)
{
// Cast the TestInteractionCb to the more general DtReceiveInteractionCb
// and pass it along with the FOM class name to the DtExerciseConn who
// actually maintains the callback lists for all interaction classes.
"Test", (DtReceiveInteractionCb) cb, usr);
// In addition, we choose to register the TestInteraction class with
// VR-Link here, by associating TestInteraction's create function with the
// right FOM class. By doing this here, we avoid making the user perform
// this registration step.
conn->interactionFactory()->addCreator("Test",
}
TestInteractionCb cb, void* usr)
{
// Cast the TestInteractionCb to the more general DtReceiveInteractionCb
// and pass it along with the FOM class name to the DtExerciseConn who
// actually maintains the callback lists for all interaction classes.
"Test", (DtReceiveInteractionCb) cb, usr);
}
{
return "Test";
}
DtExerciseConn* exConn) const
{
return new TestDecoder(exConn, classDesc());
}
DtExerciseConn* exConn) const
{
return new TestEncoder(exConn, classDesc());
}
#endif

Document ID: Generated on Thu Oct 16 00:12:25 EDT 2025 from SVN revision 280738
Copyright © 1992-2025 MAK Technologies. All Rights Reserved (www.mak.com)