VR-Link API Documentation for HLA 1.3
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
6.7 - Attribute and Parameter Encoding and Decoding

Table of Contents

Besides the class mappings described in 6.5 - Setting Up a FOM Mapper's Class Mappings, a DtFomMapper also contains information about how to encode and decode individual attributes and parameters, that is, how to go between an RTI message in FOM representation, and VR-Link's DtStateRepositories and DtInteractions.

This section describes how to configure that aspect of a FOM Mapper.

Encoders and decoders are tables of encoding and decoding functions – one for each attribute. Encoder factories and decoder factories are tables of encoders and decoders – one table for each object or interaction class. A DtFomMapper has an encoder factory and a decoder factory for both objects and interactions. Top-level VR-Link classes request encoders and decoders from a FOM Mapper's encoder and decoder factories, and use those encoders and decoders to generate or decode RTI representations of state updates or interactions.

When you build with RTI 1516, VR-Link wraps the Attribute Value Pair Map with an Attribute Value Pair Set to preserve encoder and decoder compatibility with RTI 1.3. Therefore, if you follow the directions in this chapter, your encoders and decoders will work with both RTI 1.3 and RTI 1516.

You can change the way that attributes or parameters are encoded or decoded, or provide methods for encoding or decoding new attributes or parameters, by adding your own functions to the tables used by the encoders and decoders. You can get a pointer to the encoder or decoder you want to change from the appropriate factory. To add new FOM classes, you can create new encoders and decoders and add them to the factories' tables.

6.7.1 Encoders and Decoders

At the heart of VR-Link's FOM Mapping functionality are our encoder and decoder classes:

A DtReflectedObject uses a state decoder's decode() member function to unpack data that arrives in incoming state updates or interactions (which are represented as dictated by the FOM), convert it to the representation expected by the top-level API (mutator functions in state repositories), and pass the converted data to those mutator functions.

Similarly, a DtInteraction uses an interaction decoder's decode() member function to unpack the data contained in an interaction message received from the RTI, convert it, and pass that data to the DtInteraction subclass instance's appropriate mutator functions.

The DtObjectPublisher::tick() function uses a state encoder's encode() member function to decide which attributes need to be sent, and to generate an update for those attributes. The encoder evaluates update conditions, and generates outgoing update messages by:

  1. Obtaining data from the publisher's state repository (using its inspector functions).
  2. Converting to the representations dictated by the FOM.
  3. Adding these values to an outgoing message for sending through the RTI.

Similarly, a DtInteraction uses an interaction encoder's encode() member function during sending, to convert values obtained from inspector functions to outgoing interaction messages that can be sent through the RTI.

VR-Link's encoder and decoder classes implement an efficient, table-driven approach to attribute encoding and decoding. You are free to subclass encoders and decoders and to override the encode() and decode() functions, but this is rarely necessary. Usually, you will just configure encoders and decoders, adding individual attribute or parameter encoding or decoding functions to their tables.

6.7.2 Functions for Encoding and Decoding

A decoder is implemented as a table of decoding functions, one per FOM attribute or parameter. Each function is responsible for decoding the attribute or parameter it has been associated with. Similarly, an encoder contains a table of encoding functions. For objects, an encoder contains an additional table of checking functions – functions that evaluate the update conditions for individual attributes, indicating whether the attribute needs to be sent to the exercise.

Encoders and decoders have addEncoder() and addDecoder() member functions, and DtHlaStateEncoder has an additional addChecker() member function, that allow you to register your own encoding, decoding, and checking functions for a particular attribute or parameter with an encoder or decoder. These add functions return the function that had previously been registered for the attribute or parameter, if any. The add functions create associations only for attributes or parameters of a single FOM class. If you want a function to be applied to an attribute or parameter of a class and all of its subclasses, you can call addEncoder(), addDecoder() and addChecker() on multiple encoder or decoder objects, or use the more convenient multi-class registration functions in the encoder or decoder factory classes. For more information, please see 6.7.3 Encoder and Decoder Factories.

The following sections describe the form and function of encoding, decoding, and checking functions.

6.7.2.1 Decoding Interactions

The function DtInteractionDecoder::decode() takes an RTI representation of an interaction message (an RTI::ParameterHandleValuePairSet, or PHVPS), and a pointer to a DtInteraction object, to fill out based on the RTI data. It goes through the parameters in the PHVPS, and for each one, it calls the parameter decoding function that has been associated with the parameter.

The following code sample shows the parameter decoding function used for decoding the "RateOfFire" attribute of the RPR FOM class WeaponFire into a DtFireInteraction:

void decodeRateOfFire(DtFireInteraction* inter, const RTI::ParameterHandleValuePairSet& pvlist, int index)
{
RTI::ULong length = 0;
DtNetU16* netVal = (DtNetU16*) params.getValuePointer(index, length);
int nativeVal = (DtU16) *netVal;
inter->setRate(nativeVal);
}

The general decode() function passes it a pointer to the interaction, the PHVPS containing the parameter values, and the index into the set of the parameters this decoding function should be decoding. The function:

  1. Pulls out the index'th value.
  2. Converts the value to the representation expected by the appropriate DtFireInteraction mutator function (in this case, setRate()).
  3. Passes the value to the mutator function.

In this case, the conversion is fairly trivial – from a short to an int. In addition, when VR-Link's "Net" types are used, byte swapping occurs when converting to a native type, if necessary.

6.7.2.2 Decoding Object State Updates

DtHlaStateDecoder::decode() takes a DtStateMsg (a VR-Link wrapper around an RTI::AttributeHandleValuePairSet, or AHVPS), and a pointer to the DtStateRepository it will decode the message into. It walks the list of attributes in the message, and calls the attribute decoding function that has been associated with each.

The following code sample shows a decoding function for an attribute. It is a function you might use for decoding the Position attribute of the RPR FOM object class BaseEntity into a DtEntityStateRepository. Again, index indicates which attribute in the set to decode. The conversion from FOM representation to VR-Link representation looks trivial, since a DtNet64Vector can be implicitly converted to a DtVector. Clearly, more complex conversions, such as coordinate conversions, could be added if necessary.

void decodePosition(DtEntityStateRepository* stateRep,
const RTI::AttributeHandleValuePairSet& attrs, int index)
{
RTI::ULong length = 0;
attrs.getValuePointer(index, length);
stateRep->setLocation((DtVector) *netVal);
}

6.7.2.3 Encoding Interactions

Encoders work like decoders, but in reverse. DtInteractionEncoder::encode() takes a DtInteraction instance containing interaction data, and a PHVPS to fill out with the FOM representation of this data. For each parameter in the DtInteraction's FOM interaction class, encode() calls the encoding function that has been registered with the encoder for that parameter. Parameter encoding functions look like the following example. The paramHandle argument specifies the handle (not index) of the parameter to encode. Again, the byte swapping occurs if necessary when converting from an int to a DtNetU16.

void encodeRateOfFire(
const DtFireInteraction& inter,
RTI::ParameterHandleValuePairSet* params,
RTI::ParameterHandle paramHandle)
{
DtNetU16 netVal = (DtNetU16) inter.rate();
params->add(paramHandle, (char*) &netVal, sizeof(DtNetU16));
}

6.7.2.4 Encoding Objects

Encoding objects is a little bit more complicated than encoding interactions, because the encoder has to first decide which attributes need to be sent, and then encode those attributes. A DtHlaStateEncoder contains a table of encoding functions and a table of checking functions that determine whether an attribute needs to be sent by comparing the current state of the object with the values from its previous update. Update conditions are specified in a FOM. For example, many attributes have an update condition of ON_CHANGE, meaning that they need to be sent every time their value changes.

When a publisher's tick() function calls a DtHlaStateEncoder::encode() function, it passes five arguments:

For each published attribute of the FOM class, encode() determines whether the attribute needs to be included in the update message or not. This may require calling the checker function that has been registered for the attribute. If the attribute needs to be sent, encode() calls the encoding function that has been registered for the attribute to encode it.

Encoding functions look similar to interaction encoding functions described previously. They take the state repository containing current state, the AHVPS to encode into, and the attribute handle of the attribute to be encoded.

void encodePosition(
const DtEntityStateRepository& stateRep,
RTI::AttributeHandleValuePairSet* avList,
RTI::AttributeHandle attrHandle)
{
DtNet64Vector netVal = (DtNet64Vector) stateRep.location();
avList->add(attrHandle, (char *) &netVal, sizeof(DtNet64Vector));
}

Checking functions take two state repositories as arguments: one holding the current state of the object, and one holding the state of the object as it would be seen by remote federates based on updates that the local application has sent. (The publisher decodes its outgoing updates into this as-seen-by-remote state repository in order to keep it up to date.) Many checking functions usually need to compare the current value to the as-seen-by-remote value to determine whether an attribute needs to be sent. Here's an example of a checker used for the DamageState attribute of the PhysicalEntity RPR FOM class. It returns true if the value has changed since it was last sent:

bool needDamageState(
const DtEntityStateRepository& stateRep,
const DtEntityStateRepository& asSeenByRemote)
{
return (stateRep.damageState() != asSeenByRemote.damageState())
? true : false;
}

6.7.3 Encoder and Decoder Factories

When a publisher, reflected object, or incoming interaction is created, or when an outgoing interaction is sent, it obtains from the FOM Mapper an instance of an encoder, decoder, or both that has been configured for use with its FOM class. These are the instances that they will later use to perform the encoding and decoding of FOM data.

DtFomMapper uses four factory classes to help with the job of returning appropriate encoders and decoders:

You can get pointers to a DtFomMapper's various encoder and decoder factories using the following member functions:

These factories have addEncoder() or addDecoder() member functions that allow you to associate a properly configured instance of an encoder or decoder with each FOM object or interaction class. When a publisher, reflected object, or interaction needs an instance of an encoder or decoder for its FOM class, it requests one by calling a factory's createEncoder() or createDecoder() member function. These functions return a new'ed encoder or decoder that is a clone of the instance you have associated with the class using addEncoder() or addDecoder(). Clones are created using the encoder or decoder's virtual clone() member function.

The factory classes also have encoder() and decoder() functions that return pointers to the encoder or decoder that is currently registered for a particular FOM class. The optional second argument to these functions lets you indicate what you want to happen when there is no encoder or decoder associated with a class. Use true to indicate that you want the encoder or decoder associated with the most specific superclass for which one has been registered. Use false to indicate that you want NULL to be returned when there is no encoder or decoder registered for exactly the FOM class you specified.

When you want to register your own encoding or decoding functions for a particular FOM class, use a factory's encoder() or decoder() function to get a pointer to the object containing the function table for that class. Then use the encoder or decoder's mutators to register your own functions:

RTI::InteractionClassHandle handle =
rtiAmb->getInteractionClassHandle("WeaponFire");
DtInteractionDecoder* fireDecoder =
fomMapper->interactionDecoderFactory()->decoder(handle);
fireDecoder->addDecoder("RateOfFire", myFunc);

When you use this method to register functions, you are associating new encoding, decoding, or checking functions only with a single FOM class at a time. If you want to add such functions to the encoders and decoders for a class and all of its subclasses, you can use one of the following member functions of one of the factory classes:

These functions takes the name (or handle) of a FOM class, the name (or handle) of an attribute or parameter, a function to register, and a boolean flag indicating whether the function should apply to subclasses of the indicated FOM class (default is true).

For example, to register a new encoding function for the Position attribute of the BaseEntity RPR FOM class and all of its subclasses:

fomMapper->stateEncoderFactory()->addAttributeEncoder("BaseEntity", "Position", myFunc, true);

You can replace any of the factories used by a FOM Mapper using the factory_typesetFactory() functions. For example, if you are creating a FOM Mapper for a new FOM, you might create a subclass of DtHlaStateEncoderFactory whose constructor registers all of the desired encoder prototypes. You can tell the FOM Mapper to use an instance of this subclass like this:

MyEncoderFactory* factory = new MyEncoderFactory(...);
fomMapper->setStateEncoderFactory(factory);

Alternatively, you can start with the empty factories, or the factories contained in some existing FOM Mapper (like VR-Link's RPR FOM Mapper), and just add your own encoders and decoders to them without ever creating new factory instances.

[<< Creating Instances of DtInteraction] [Home] [Top of Page] [Choosing a DtFomMapper >>]


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)