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

Table of Contents

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

  1. Launch two testPduDIS applications from your VR-Link install's bin64 directory.
  2. 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.
  3. 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:

  1. 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".
  2. 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.
  3. 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.
  4. 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:

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:

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:

  1. Gets the old value of "A".
  2. Either pushes down or pulls up the rest of the PDU data depending on whether the value of "A" is increasing or decreasing.
  3. Sets the value of the "A" field.

To implement the inspector and mutator for B:

  1. 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.
  2. 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

/****************************************************************************
* Copyright (c) 1992-2025 MAK Technologies, Inc
* All rights reserved.
****************************************************************************/
#include "testPdu.h"
#include <iostream>
void varCb( TestPdu* pdu, void* )
{
std::cout << "Received Test PDU!" << std::endl;
pdu->print();
std::cout << std::endl;
}
int main( int argc, char* argv[] )
{
// Used for error handling
DtINIT_MINIDUMPER( "PduTest" );
// Send DIS 7 PDUs.
DtExerciseConn conn( 3000, 1, 1, 1 );
DtClock* clock = conn.clock();
// Register the callback on incoming TestPdus.
TestPdu::addCallback(&conn, varCb, NULL);
while (1)
{
clock->setSimTime(clock->absRealTime());
conn.drainInput();
if (input.keybrdTick() == -1) break;
// Send a TestPdu.
TestPdu pdu;
pdu.setA(4);
pdu.setB(0,1);
pdu.setB(1,1);
pdu.setB(2,1);
pdu.setB(3,1);
pdu.setC(10.0);
conn.sendStamped(pdu);
// Now change the length of "b" (the value of "a") to 2 and send again.
pdu.setA(2);
conn.sendStamped(pdu);
DtSleep(1.0);
}
return 0;
}

TestPdu Class

TestPdu Header File

/*********************************************************************
** Copyright (c) 1992-2025 MAK Technologies, Inc
** All rights reserved.
*********************************************************************/
#pragma once
#if DtDIS
#include <vl/pdu.h>
typedef struct NetTestPdu
{
class TestPdu;
typedef void (*TestPduCb)(TestPdu* pdu, void* usr);
class TestPdu : public DtPdu
{
public:
TestPdu(const NetTestPdu *initial,
DtBufferPtr buffer = DtUSE_INTERNAL_BUFFER, DtPduFactory* pduFactory = 0);
virtual ~TestPdu();
TestPdu(const TestPdu& orig);
TestPdu& operator=(const TestPdu& orig);
virtual void printDataToStream(std::ostream& stream) const;
virtual void setA(int val);
virtual int a() const;
virtual void setB(int index, int val);
virtual int b(int index) const;
virtual void setC(float val);
virtual float c() const;
virtual NetTestPdu* netTestPdu();
virtual const NetTestPdu* netTestPdu() const;
public:
static DtPdu* create(const DtNetPacket *initial,
DtBufferPtr buffer = DtUSE_INTERNAL_BUFFER, DtPduFactory* pduFactory = 0);
static void addCallback(DtExerciseConn *conn, TestPduCb cb, void *usr);
static void removeCallback(DtExerciseConn *conn, TestPduCb cb, void *usr);
protected:
virtual DtPduKind internalGetPduKind() const;
};
#endif

TestPdu Source File

/*********************************************************************
** Copyright (c) 1992-2025 MAK Technologies, Inc
** All rights reserved.
*********************************************************************/
#if DtDIS
#include "testPdu.h"
#include "vl/pduFactory.h"
#include <iostream>
// Blank constructor.
{
// Must call initPdu, passing the minimal size of this type of PDU, and the
// buffer pointer passed to us. Here, minimal size occurs when "b" has 0
// elements. 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".
int minimalSize = sizeof(DtNetPduHeader) + sizeof(DtNetInt32) +
sizeof(DtFloat32);
initPdu(minimalSize, buffer);
}
{
}
// From-network-representation constructor.
TestPdu::TestPdu(const NetTestPdu *initial, DtBufferPtr buffer, DtPduFactory* pduFactory)
{
// Must call initPdu, passing the network representation and the buffer
// that were passed to us.
initPdu(initial, buffer, pduFactory);
}
// Copy constructor.
TestPdu::TestPdu(const TestPdu& orig) :
DtPdu(orig)
{
}
// Assignment operator.
{
// Guard against assignment to yourself.
if (this == &orig)
{
return *this;
}
return *this;
}
void TestPdu::printDataToStream(std::ostream& stream) const
{
std::cout << "A: " << a() << "\n"
<< "B: \n";
// Remember that a() indicates the length of array "b".
for (int index = 0; index < a(); index++)
{
std::cout << b(index) << '\n';
}
std::cout << "C: "<< c() << std::endl;
}
void TestPdu::setA(int val)
{
// Since "a" represents the number of elements in the "b" array, we must
// insert or delete bytes from the PDU layout whenever we change "a"
// through this mutator.
// Get the old value of "a".
int oldA = a();
if (val > oldA)
{
// Insert space for (val - oldA) 32-bit integers after the current last
// element of the "b" array. insertBytes will push the rest of the PDU
// data (in this case just "c") down past the end of the byte being
// added.
insertBytes(&(netTestPdu()->b[oldA]),
(val - oldA) * sizeof(DtNetInt32));
}
else if (val < oldA)
{
// Delete space used by the last (oldA - val) 32-bit integers.
// deleteBytes pulls up the rest of the PDU data (in this cast just "c",
// so that it occupies the space vacated by the deleted bytes.
deleteBytes(&(netTestPdu()->b[val]), (oldA - val) * sizeof(DtNetInt32));
}
// Now set the value of the "a" field.
netTestPdu()->a = val;
}
int TestPdu::a() const
{
return netTestPdu()->a;
}
void TestPdu::setB(int index, int val)
{
// Do bounds checking, then set the index'th element of the "b" array to
// val.
if (index < 0 || index > a())
{
std::cout << "TestPdu::setB(): index out of range.\n";
return;
}
netTestPdu()->b[index] = val;
}
int TestPdu::b(int index) const
{
// Do bounds checking, then return the index'th element of the "b" array.
if (index < 0 || index > a())
{
std::cout << "TestPdu::b(): index out of range.\n";
return 0;
}
return netTestPdu()->b[index];
}
void TestPdu::setC(float val)
{
// We can find "c" after the last element of "b".
DtNetFloat32* netC = (DtNetFloat32*) &(netTestPdu()->b[a()]);
// val will be implicitly cast to a DtNetFloat32.
*netC = val;
}
float TestPdu::c() const
{
// We can find "c" after the last element of "b".
DtNetFloat32* netC = (DtNetFloat32*) &(netTestPdu()->b[a()]);
// netC will be implicitly cast to a native float.
return *netC;
}
{
return (NetTestPdu*) packet();
}
{
return (const NetTestPdu*) packet();
}
DtPdu* TestPdu::create(const DtNetPacket *initial, DtBufferPtr buffer, DtPduFactory* pduFactory)
{
// Return a new'ed instance of this class.
return new TestPdu((NetTestPdu *) initial, buffer, pduFactory);
}
void TestPdu::addCallback(DtExerciseConn *conn, TestPduCb cb, void *usr)
{
// Register this class' create function with the DtExerciseConn's
// pduFactory so that the DtExerciseConn will know to create an instance of
// this class to represent incoming PDUs of the appropriate kind. If this
// is not done here, the user of this class will have to explicitly register
// the class with the PDU factory himself.
conn->pduFactory()->addCreator(TestPduKind, TestPdu::create);
// Cast the TestPduCb to the more general DtPduCallbackFcn and pass it along
// with the PDU kind to the DtExerciseConn who actually maintains the
// callback lists for all PDU kinds.
conn->addPduCallback(TestPduKind, (DtPduCallbackFcn) cb, usr);
}
{
// Cast the TestPduCb to the more general DtPduCallbackFcn and pass it along
// with the PDU kind to the DtExerciseConn who actually maintains the
// callback lists for all PDU kinds.
conn->removePduCallback(TestPduKind, (DtPduCallbackFcn) cb, usr);
}
{
// We defined TestPduKind in testPdu.h as DtPduKind(220).
return TestPduKind;
}
#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)