The Test PDU example is an DIS-specific example that demonstrates how to derive a new PDU from DtPdu.
When run, the application will send and receive the new Test PDUs.
VR-Link's DtExerciseConn::send() and DtExerciseConn::sendStamped() functions can send any type of PDU, as long as the object passed to it is of a type derived from DtPdu. So, to send user-defined PDUs, you can write a derived class, after which you can use your derived class with these functions.
When DtExerciseConn receives a packet from the network, it creates an appropriate DtPdu based on the PDU kind in the packet. For instance, a DtEntityStatePdu is created when a DIS packet is received with PDU kind equal to DtEntityStatePduKind. In order for VR-Link to know which PDU class to create for a given PDU kind, the PDU class must be registered with the PDU factory. The registration of PDU class and kind with the PDU factory is usually performed in the PDU class's addCallback() function. That way, only PDU kinds that have a callback will be processed.
Your class should provide the static member functions addCallback() and removeCallback() for callback management, and the static member function create(), that returns a new instance of the class. Remember to add the PDU class to the PDU factory in the add callback method.
The Test PDU example creates a variable length PDU. The TestPdu class represents a hypothetical variable-length "Test PDU" - one that has three fields: an integer named "A", a variable-length array of integers named "B" whose cardinality is the value of "A", and a float called "C".
How to Run the Example
- Launch two testPduDIS applications from your VR-Link install's bin64 directory.
- Once running, you can observe the Test PDUs sent between the two applications. The PDUs will include the three new fields, A, B, and C.
- Note that the value of A will alternate between A=4 and A=2. This will control the length of array B as can be observed in the console printouts.
Extending the PDU
The TestPdu class extends DtPdu and holds the Number and Vector data.
Deriving a Class from DtPdu
To derive a class from DtPdu:
- Create the PDU layout, a structure that is used for the network representation of the PDU.
Network representations for all of the VR-Link PDUs are in the include/packets directory.
By using our "Net" types, such as DtNetInt32 (defined in NetTypes.h), you ensure platform independence. When you are on little endian machines, byte swapping is performed when assigning to a Net type, and when a Net type is implicitly cast to a native type.
The first field in all network representations should be a DtNetPduHeader.
If you are creating a fixed-length PDU, the structure should describe the entire layout of the PDU. Since this PDU is variable length, it is not possible to fully describe the PDU layout.
Define as much of the network representation structure as is possible with a C-style structure. The cardinality of "B" in the structure does not matter, since we will only be casting buffers to pointers to this structure, and not creating instances of the structure or relying on its size. We will not be able to access "C" at all through the structure, since it comes after the variable length array "B". We will have to use byte-arithmetic to reach "C".
- Extend the DtPduKind enumeration, adding our chosen value. This is the number that ends up in the PDU header's kind field.
The value you choose for PDU kind should not be one that is used by any other PDU. We recommend a value between 221 and 255.
- Create a type called TestPduCb. Functions of this type can be registered as callbacks to be called on receipt of TestPDUs. Forward declaration of TestPdu is necessary first.
- Create the TestPdu class definition.
- Each class derived from DtPdu must have the two standard constructors possessed by all PDU classes. Your DtPdu can have additional constructors, but it must have the two standard ones.
- PDU classes must provide a definition for the virtual function internalGetPduKind(), which returns the PDU kind value being used for this type of PDU.
- Your class should provide the static member functions addCallback() and removeCallback() for callback management, and the static member function create(), that returns a new instance of the class.
- Most DtPdu sub-classes provide inspector and mutator functions to examine and set the fields of your PDU, but VR-Link does not require that you do so.
In testPdu.cxx, we do the following:
- Define a blank PDU constructor
The size passed to initPdu() is the size of the smallest legal TestPdu; that is, one with zero elements in the array B. The size of the PDU is therefore the size of the header plus the size of the DtNetInt32 field "A" plus the size of the DtNetFloat32 field "C".
initPdu() is a member of the base class DtPdu, and it handles allocating memory for the network representation of the PDU, and other related items.
- Note
- If this was a fixed-length PDU, the size would be the size of the NetTestPdu structure.
- Define the from-network-representation constructor
- Define internalGetPduKind() It should return the value 225.
- Implement the create() function
- Implement callback registration functions
- Implement mutator and inspector functions
Implementing Mutator and Inspector Functions
For a fixed-length PDU, writing mutator and inspector functions is fairly straightforward. A pointer to the PDU's network representation is stored in a DtPdu member and can be obtained using DtPdu::packet(). The void* returned must be cast to a pointer to your network representation structure before fields are examined and set within your accessors. We define the function netTestPdu() to perform this cast. In our test PDU example, we can then use this function to obtain a pointer to the PDU's network representation structure.
When you write mutators for variable length PDUs, like the testPdu example, you need to call one of the following DtPdu member functions every time you change the size or layout of the PDU:
- insertBytes()
- deleteBytes()
In addition, you may need to do some pointer arithmetic to access certain fields of the PDU that cannot be accessed through members of the static structure.
The insertBytes() function inserts the indicated number of blank (zeroed) bytes at the desired offset into the PDU's network representation.
The deleteBytes() function deletes the desired number of bytes, starting with the indicated offset. These functions handle reallocating the memory for the network representation if necessary, and will also make sure the size field in the header is updated to reflect the new size.
Since a represents the number of elements in the array B, we must insert or delete bytes from the PDU layout whenever we change A through its mutator.
To implement the mutator for A, the code does the following:
- Gets the old value of "A".
- Either pushes down or pulls up the rest of the PDU data depending on whether the value of "A" is increasing or decreasing.
- Sets the value of the "A" field.
To implement the inspector and mutator for B:
- The b() and setB() functions should take an index into the array, so that we know which element to inspect or set. We can do some bounds checking if we want.
- Either do bounds checking, then set the index'th element of the "B" array to val, or do bounds checking, then return the index'th element of the "B" array.
The inspector and mutator for the C field must do some pointer arithmetic to find where the data is stored, since it cannot be accessed through the structure.
Test PDU Example Code
Main Application
#include <iostream>
{
std::cout << "Received Test PDU!" << std::endl;
std::cout << std::endl;
}
int main( int argc, char* argv[] )
{
while (1)
{
}
return 0;
}
TestPdu Class
TestPdu Header File
#pragma once
#if DtDIS
{
{
public:
virtual void setA(
int val);
virtual void setB(
int index,
int val);
virtual int b(
int index)
const;
virtual void setC(
float val);
public:
protected:
};
#endif
TestPdu Source File
#if DtDIS
#include <iostream>
{
initPdu(minimalSize, buffer);
}
{
}
{
initPdu(initial, buffer, pduFactory);
}
{
}
{
if (this == &orig)
{
return *this;
}
return *this;
}
{
std::cout <<
"A: " <<
a() <<
"\n"
<< "B: \n";
for (
int index = 0; index <
a(); index++)
{
std::cout <<
b(index) <<
'\n';
}
std::cout <<
"C: "<<
c() << std::endl;
}
{
if (val > oldA)
{
}
else if (val < oldA)
{
}
}
{
}
{
if (index < 0 || index >
a())
{
std::cout << "TestPdu::setB(): index out of range.\n";
return;
}
}
{
if (index < 0 || index >
a())
{
std::cout << "TestPdu::b(): index out of range.\n";
return 0;
}
}
{
*netC = val;
}
{
return *netC;
}
{
}
{
}
{
}
{
}
{
}
{
}
#endif