VR-Engage  2.2
Loading...
Searching...
No Matches
Custom PDU Example

Overview

Purpose: This example shows how to implement a custom Protocol Data Unit (PDU) / interaction in VR-Engage using a comment messaging pattern. It fully implements the receive/display and network translation paths for a Comment PDU or interaction, and demonstrates how you would wire an outbound send path once you add your own UI or scripting.

Observable Behavior: When running this example with the comment plugins enabled and a role that includes DtCommentLogic and DtCommentConnector, your station displays incoming Comment PDUs or interactions as entries in a "Comment" action menu. Each received comment targeting your entity appears as a menu item you can dismiss; dismissing removes that specific comment. If you add UI that publishes CommentMessage instances or drive the network from an external tool such as VR-Forces, you can exercise the full send/receive path.

Prerequisites: Understanding of VR-Engage frontend-backend architecture and familiarity with the message system and event delegation. Knowledge of VR-Link networking (DIS/HLA protocols) and basic understanding of DIS PDU structures or HLA interactions is required.

Related Examples: The Vehicle Blinker Example covers frontend-backend coordination, while the Input Device Example addresses custom UI integration.


Key concepts demonstrated

This example implements custom message definition for application-specific message types through message schemas defined in Lua files. Code generation creates C++ message classes with type-safe, strongly-typed fields using declarative message definition with automatic serialization.

The frontend UI component provides the user interface for receiving and dismissing comments through DtCommentLogic. It integrates with the VR-Engage action menu system, handles received comments and user dismissal actions, and displays dynamic menu items for active comments; composing or sending new comments from the UI is left to your own code.

The network connector pattern bridges internal messages and network PDUs using DtCommentConnector to translate between internal messages and network PDUs. It registers VR-Link callbacks for incoming PDU notifications and converts outgoing messages to DIS Comment PDUs or HLA Comment Interactions; the outbound path is exercised whenever your own UI, scripts, or backend systems publish CommentMessage instances.

The three-component architecture provides separation of concerns with commentMessage as the shared message definition library, commentFrontend handling comment display and user dismissal interaction in the frontend process, and commentNet managing network transmission through per-protocol plugins for DIS, HLA1516e, and HLA4.


Architecture

flowchart LR
   subgraph Frontend["VR-Engage Frontend"]
      commentLogic["DtCommentLogic\n(Comment UI Component)"]
   end

   subgraph Connector["Network Connector Plugin\n(commentDIS, commentHLA1516E, etc.)"]
      commentConnector["DtCommentConnector\n(Protocol Adapter)"]
      exerciseConn["DtExerciseConn\n(VR-Link Exercise Connection)"]
   end

   network["DIS / HLA Exercise Network\n(Other Participants)"]

   commentLogic -- CommentMessage --> commentConnector
   commentConnector -- sendStamped(DIS/HLA Comment) --> exerciseConn
   exerciseConn <-- DIS/HLA Comment --> network

   network -- DIS/HLA Comment --> exerciseConn
   exerciseConn -- callback(commentCb) --> commentConnector
   commentConnector -- CommentMessage --> commentLogic

Component Responsibilities:

Component Process Library Type Responsibilities
commentMessage Shared SHARED library Message schema definition, serialization/deserialization
commentFrontend Frontend MODULE (plugin) UI integration, received comment display, dismissal actions
commentNet Frontend MODULE (plugin) Network transmission, PDU conversion, protocol handling

Message Flow (Sending Comment) (pattern to follow once you add a sender):

  1. Your UI, script, or backend logic initiates a "send comment" action (this example does not implement this step)
  2. That code creates a CommentMessage with sender, receiver, and comment text
  3. Message is published via DtVreMessageManager
  4. DtCommentConnector receives the message (sender matches local entity ID)
  5. Connector creates a DtCommentInteraction VR-Link object
  6. PDU is transmitted via DtExerciseConn::sendStamped()
  7. PDU is sent over the network to the receiver

Message Flow (Receiving Comment):

  1. DIS Comment PDU or HLA Comment Interaction received by VR-Link
  2. VR-Link invokes registered callback commentCb()
  3. Callback forwards to DtCommentConnector::handleCommentInteraction()
  4. Connector checks if PDU intended for local entity
  5. Connector creates CommentMessage with sender/receiver/comment
  6. Message published via DtVreMessageManager
  7. DtCommentLogic::handleCommentMessage() receives message
  8. Comment added to action menu as new menu element
  9. User sees comment in UI

Code walkthrough

Message definition (Lua schema)

Custom messages defined declaratively in Lua:

-- File: examples/customPdu/commentMessage/comment.lua
MESSAGE{
fileName="commentMessage"; -- Generated filename base
className="Comment"; -- C++ class name will be CommentMessage
messageType="admin.comment"; -- Message type identifier
comment = "This class is used with the exampleCustomPdu example";
attributes={
{type = "EntityIdentifier", name = "sender"};
{type = "EntityIdentifier", name = "receiver"};
{type = "String", name = "comment"};
}
}

The generateMessages() CMake function processes .lua files to create commentMessage.h and commentMessage.cxx files. The generated class inherits from DtVreMessage base with accessor/mutator methods created for each attribute. Serialization/deserialization is automatically implemented, and the message type string is used for handler registration.

Generated Interface (conceptual, actual file auto-generated):

class CommentMessage : public DtVreMessage
{
public:
static CommentMessage* create();
static const char* theType(); // Returns "admin.comment"
DtEntityIdentifier getSender() const;
void setSender(const DtEntityIdentifier& sender);
DtEntityIdentifier getReceiver() const;
void setReceiver(const DtEntityIdentifier& receiver);
std::string getComment() const;
void setComment(const std::string& comment);
};

Frontend: comment logic component

The frontend component manages UI display of comments:

// File: examples/customPdu/commentFrontend/commentLogic.h
namespace makVre
{
class DtCommentLogic : public DtPlayerComponent
{
public:
virtual ~DtCommentLogic();
virtual const char* type() const override;
virtual bool initialize(DtPlayerStation* player, DtInitTable& config);
virtual void tick(double dt);
virtual void shutdown();
protected:
DtMenu* myMenu; // Action menu for displaying comments
};
}
virtual void tick(double dt) override
Per-frame update hook. This example does not require per-tick work but keeps the standard component i...
DtMenu * myMenu
Menu used to present received comments to the player.
Definition commentLogic.h:60
DtCommentLogic()
Constructs a new comment logic component with no menu attached yet.
virtual DtVreMessageResult handleMenuMessage(makVre::DtVreMessage *msg)
Handles menu action messages generated when the user interacts with the comment menu (for example,...
virtual void shutdown() override
Cleans up message handlers and releases any menu resources on shutdown.
virtual ~DtCommentLogic() override
Virtual destructor to allow safe subclassing.
virtual bool initialize(DtPlayerStation *player, DtInitTable &config) override
Initializes the component, registers message handlers, and creates the comment menu if a menu manager...
virtual const char * type() const override
Returns the component type string used during factory registration.
virtual DtVreMessageResult handleCommentMessage(makVre::DtVreMessage *msg)
Handles internal CommentMessage instances produced by the network connector and adds corresponding en...
virtual DtPlayerStation & player()
Gets the player station.
Abstract base class for all VREngage messages.
Definition vreMessage.h:50
Include export definitions for this library.
Definition glsVreMessageUtil.h:49
DtVreMessageResult
Enumeration of possible message handling results.
Definition vreMessage.h:33

This class inherits from DtPlayerComponent as a generic frontend component, maintains a reference to the action menu for comment display, and implements two message handlers for comments and menu actions respectively.

Frontend: initialization and menu creation

Initialize creates the action menu and registers message handlers:

// File: examples/customPdu/commentFrontend/commentLogic.cxx
bool DtCommentLogic::initialize(DtPlayerStation* player, DtInitTable& config)
{
if (!DtPlayerComponent::initialize(player, config))
return false;
// Register handler for incoming comment messages
DtVreMessageManager::instance().addHandler(
CommentMessage::theType(),
DtVreMessageDelegate(this, &DtCommentLogic::handleCommentMessage));
// Register handler for menu action messages (user dismissal)
DtVreMessageManager::instance().addHandler(
MenuActionMessage::theType(),
DtVreMessageDelegate(this, &DtCommentLogic::handleMenuMessage));
// Create custom action menu for displaying comments
if (DtMenuManager* manager = playerAttributeStore()->getAttribute<DtMenuManager*>("menuManager"))
{
myMenu = new DtMenu(*manager);
myMenu->setName("Comment");
myMenu->setTitle("Received Comments");
manager->addMenu(myMenu);
}
return true;
}

DtVreMessageDelegate creates type-safe callback binding while CommentMessage::theType() returns the message type string for filtering. The menu manager is retrieved from the player attribute store as a shared service, with the menu initially empty and elements added dynamically when comments are received.

Frontend: handling received comments

When comment arrives, add it to the menu and display:

// File: examples/customPdu/commentFrontend/commentLogic.cxx
DtVreMessageResult DtCommentLogic::handleCommentMessage(makVre::DtVreMessage* msg)
{
if (CommentMessage* cmsg = dynamic_cast<CommentMessage*>(msg))
{
// Only display comments intended for me and not sent by me
if (cmsg->getReceiver() == myPlayer->entityId() &&
cmsg->getSender() != myPlayer->entityId())
{
// Create new menu element for this comment
DtMenuElement* element = new DtMenuElement(
cmsg->getComment(), // Display text
"closeComment", // Action identifier
cmsg->getSender().string()); // Parameter (sender ID for tracking)
myMenu->addElement(element);
// Show the comment menu
MenuSetStateMessage* mmsg = MenuSetStateMessage::create();
mmsg->setMenu("Comment");
mmsg->setType(MenuSetStateMessage::Type_STATIC);
mmsg->setState(MenuSetStateMessage::State_SHOW);
// (Note: Message not queued in shown code, typically would be)
}
return HANDLED;
}
return IGNORED;
}
@ IGNORED
The handler did not process the message; continue processing.
Definition vreMessage.h:34

Filter logic prevents self-echoing through sender != receiver checks, while the menu element action "closeComment" enables user dismissal. The element parameter stores the sender ID for element identification, and the HANDLED return value indicates the message was fully processed.

Frontend: handling comment dismissal

User clicks to dismiss comment from menu:

// File: examples/customPdu/commentFrontend/commentLogic.cxx
DtVreMessageResult DtCommentLogic::handleMenuMessage(makVre::DtVreMessage* msg)
{
if (MenuActionMessage* mmsg = dynamic_cast<MenuActionMessage*>(msg))
{
if (mmsg->getAction() == "closeComment")
{
// Close the menu
myMenu->closeMenu();
// Find and remove the menu element matching the action parameter
std::vector<DtMenuElement*> elements = myMenu->elements();
for (int i = 0; i < elements.size(); i++)
{
if (elements[i]->parameter() == mmsg->getParameter())
{
myMenu->removeElement(elements[i]);
break;
}
}
return HANDLED;
}
}
return IGNORED;
}
@ HANDLED
The handler processed the message; continue processing.
Definition vreMessage.h:35

Parameter matching allows identification of specific comments to dismiss while supporting multiple active comments simultaneously. The menu automatically hides when all elements are removed.

Frontend: shutdown cleanup

Unregister handlers to prevent dangling references:

// File: examples/customPdu/commentFrontend/commentLogic.cxx
void DtCommentLogic::shutdown()
{
// Unregister message handlers
DtVreMessageManager::instance().removeHandler(
CommentMessage::theType(),
DtVreMessageDelegate(this, &DtCommentLogic::handleCommentMessage));
DtVreMessageManager::instance().removeHandler(
MenuActionMessage::theType(),
DtVreMessageDelegate(this, &DtCommentLogic::handleMenuMessage));
}

Network connector: structure

The connector bridges internal messages and network PDUs:

// File: examples/customPdu/commentNet/commentConnector.h
namespace makVre
{
class DtCommentConnector : public DT_PROTOCOL_NAMESPACE::DtSimEventConnector
{
public:
virtual void install(DtPlayerStationApp* app,
makVrv::DT_PROTOCOL_NAMESPACE::DtVrlinkConnection* connection);
virtual void tick();
virtual void shutdown();
virtual void handleCommentInteraction(DtCommentInteraction* comment);
virtual DtVreMessageResult handleCommentMessage(DtVreMessage* msg);
protected:
DtEntityIdentifier myEntityId;
};
}
DtEntityIdentifier myEntityId
DIS/HLA entity identifier for the local player station entity.
Definition commentConnector.h:57
virtual void shutdown() override
Removes network and message callbacks and releases connector resources.
virtual DtVreMessageResult handleCommentMessage(DtVreMessage *msg)
Handles an internal CommentMessage and, when sent by the local entity, constructs and transmits a cor...
DtCommentConnector()
Constructs a connector with no associated player entity identifier.
virtual ~DtCommentConnector() override
Virtual destructor to support polymorphic deletion.
virtual void install(DtPlayerStationApp *app, makVrv::DT_PROTOCOL_NAMESPACE::DtVrlinkConnection *connection) override
Installs the connector with the given player station application and VR-Link connection,...
virtual void tick() override
Per-frame update hook. The example does not require periodic work but keeps the standard connector in...
virtual void handleCommentInteraction(DtCommentInteraction *comment)
Handles an incoming Comment interaction from the exercise connection and, when addressed to the local...

This class inherits from DtSimEventConnector as the base for network event handlers. The DT_PROTOCOL_NAMESPACE macro expands to protocol-specific namespaces (DIS, HLA1516e, etc.), while handleCommentInteraction() processes incoming VR-Link PDUs and handleCommentMessage() processes outgoing internal messages. It stores the local entity ID for filtering purposes.

Network connector: installation

Install registers VR-Link callback and message handler:

// File: examples/customPdu/commentNet/commentConnector.cxx
// VR-Link callback function (C-style callback required by VR-Link API)
void commentCb(DtCommentInteraction* comment, void* usr)
{
// Forward to member function
((DtCommentConnector*)usr)->handleCommentInteraction(comment);
}
void DtCommentConnector::install(DtPlayerStationApp* app,
makVrv::DT_PROTOCOL_NAMESPACE::DtVrlinkConnection* connection)
{
// Base class install
DtSimEventConnector::install(app, connection);
// Get local entity ID from player station
if (app && app->players().size() > 0)
{
DtPlayerStation* playerStation = app->players().back();
myEntityId = playerStation->entityId();
}
// Register VR-Link callback for incoming Comment PDUs
DtCommentInteraction::addCallback(connection->exerciseConn(), commentCb, this);
// Register message handler for outgoing comment messages
DtVreMessageManager::instance().addHandler(
CommentMessage::theType(),
DtVreMessageDelegate(this, &DtCommentConnector::handleCommentMessage));
}
DtDelegate< DtVreMessageResult, DtVreMessage * > DtVreMessageDelegate
Delegate type for message handler callbacks.
Definition vreMessage.h:212

VR-Link uses C-style callbacks, requiring the global commentCb function, while the user pointer (usr) passes the this pointer for context. The DtCommentInteraction::addCallback() method registers for PDU notification, and the local entity ID is cached for filtering incoming and outgoing messages.

Network connector: receiving PDUs

Convert incoming PDU to internal message:

// File: examples/customPdu/commentNet/commentConnector.cxx
void DtCommentConnector::handleCommentInteraction(DtCommentInteraction* comment)
{
// Only process PDUs intended for local entity
if (comment->receiverId() == myEntityId)
{
// Create internal message from PDU data
CommentMessage* msg = CommentMessage::create();
msg->setReceiver(myEntityId);
msg->setSender(comment->senderId());
msg->setComment(comment->comment());
// Publish to internal message system
DtVreMessageManager::instance().queueMessage(msg);
}
}

Filtering by receiver ID prevents processing broadcasts not intended for this entity. DtCommentInteraction serves as the VR-Link wrapper for Comment PDUs, with messages created dynamically using factory methods and queueMessage() providing asynchronous delivery processed in the next frame.

Network connector: sending PDUs

Convert outgoing message to PDU:

// File: examples/customPdu/commentNet/commentConnector.cxx
DtVreMessageResult DtCommentConnector::handleCommentMessage(DtVreMessage* msg)
{
if (CommentMessage* cmsg = dynamic_cast<CommentMessage*>(msg))
{
// Only process messages originating from local entity
if (cmsg->getSender() == myEntityId)
{
// Create VR-Link Comment interaction object
DtCommentInteraction cmt;
cmt.setSenderId(myEntityId);
cmt.setReceiverId(cmsg->getReceiver());
cmt.setComment(cmsg->getComment().c_str(), cmsg->getComment().size());
// Transmit PDU over network
myConnection->exerciseConn()->sendStamped(cmt);
}
return HANDLED;
}
return IGNORED;
}

Filtering by sender ID prevents re-transmitting received messages, while the DtCommentInteraction constructor populates PDU fields. The sendStamped() method adds timestamps and transmits immediately, with protocol-specific serialization handled automatically by VR-Link.

Network connector: shutdown

Cleanup removes callbacks and handlers:

// File: examples/customPdu/commentNet/commentConnector.cxx
void DtCommentConnector::shutdown()
{
// Unregister VR-Link callback
DtCommentInteraction::removeCallback(myConnection->exerciseConn(), commentCb, this);
// Unregister message handler
DtVreMessageManager::instance().removeHandler(
CommentMessage::theType(),
DtVreMessageDelegate(this, &DtCommentConnector::handleCommentMessage));
}

Component registration

Each plugin registers its components:

// File: examples/customPdu/commentFrontend/plugin.cxx
{
LOG_VERBOSE("COMMENT") << "Initializing example custom pdu front-end" << std::endl;
// Register frontend component
app->componentFactory().addCreator<DtCommentLogic>("DtCommentLogic");
return true;
}
void addCreator(std::string name="")
Registers a creator for a specific type.
Definition factory.h:112
Top-level class representing the VR-Engage application.
Definition playerStationApp.h:163
virtual DtComponentFactory & componentFactory()
Gets the component factory.
VRECOMMONCOMPONENTS_DLL bool initPlayerStationModule(makVre::DtPlayerStationApp *app)
Module initialization function declarations using C linkage.
#define LOG_VERBOSE(channel)
Macro to log a verbose message to log files.
Definition logger.h:79
// File: examples/customPdu/commentNet/plugin.cxx
// Helper function to generate protocol-specific plugin name
std::string networkProtocolPostfix()
{
std::string postfix;
#ifdef DtDIS
postfix += "-DIS";
#elif defined(DtHLA_4)
postfix += "-HLA4";
#elif defined(DtHLA_1516_EVOLVED)
postfix += "-HLA1516E";
#elif defined(DtHLA_1516)
postfix += "-HLA1516";
#else
postfix += "-HLA13";
#endif
return postfix;
}
{
LOG_VERBOSE("COMMENT") << "Initializing example custom pdu back-end" << std::endl;
// Register connector with protocol-specific name
app->connectorFactory().addCreator<DtCommentConnector>(
"DtCommentConnector" + networkProtocolPostfix());
return true;
}
virtual DtConnectorFactory & connectorFactory()
Gets the connector factory.

Network connectors are built separately for each protocol (DIS, HLA1516e, HLA4), with protocol-specific macros (DtDIS, DtHLA_1516_EVOLVED, etc.) defined by the build system. Connector names include protocol suffixes for disambiguation.


Deployment and testing

Installation

Build all components (see Environment Setup & Build Guide):

cd examples\build
REM Build message library (shared dependency)
cmake --build . --config RelWithDebInfo --target commentMessage
REM Build frontend plugin
cmake --build . --config RelWithDebInfo --target commentFrontend
REM Build network connector plugins (one per protocol)
cmake --build . --config RelWithDebInfo --target commentDIS
cmake --build . --config RelWithDebInfo --target commentHLA1516e
cmake --build . --config RelWithDebInfo --target commentHLA4

Install all plugins to the VR-Engage installation:

cmake --install . --config RelWithDebInfo

This copies:

  • Message library to <VR-Engage-Install-Dir>\bin64\exampleCommentMessage.dll
  • Frontend plugin to <VR-Engage-Install-Dir>\plugins64\vrEngage\release\exampleCommentFrontend.dll
  • Network plugins to <VR-Engage-Install-Dir>\plugins64\vrEngage\release\exampleCommentDIS.dll (and HLA variants)

Verify installation:

dir "<VR-Engage-Install-Dir>\bin64\exampleCommentMessage.dll"
dir "<VR-Engage-Install-Dir>\plugins64\vrEngage\release\exampleCommentFrontend.dll"
dir "<VR-Engage-Install-Dir>\plugins64\vrEngage\release\exampleCommentDIS.dll"

Configuration

Frontend plugins load automatically: VR-Engage automatically discovers and loads all plugins in the plugins64/vrEngage/release/ directory at startup. No plugin manifest configuration is required for frontend plugins.

Example simulation model set (VR-Forces integration):

If you are running VR-Engage against a VR-Forces backend, the easiest way to configure this example is to use the provided simulation model set vrengage/data/data/simulationModelSets/examples/customPdu.sms. It configures a DIS exercise connection on localhost and uses the human role vrengage/data/data/simulationModelSets/examples/customPdu/roles/human.lua, which adds the DtCommentLogic component and DtCommentConnector so that incoming Comment PDUs or interactions targeting your human entity will appear in the Comment menu in VR-Engage.

The role file configures the components and connectors as follows:

inherits = "@(vre-roles-dir)/humanBase.lua";
components = {
["commentLogic"] = {
componentType = "DtCommentLogic";
priority = 5;
};
};
connectors = {
"DtCommentConnector";
};

When you launch VR-Engage with this simulation model set selected, no additional role or connection configuration is required for the example.

Manual integration without the example SMS:

If you are not using the customPdu.sms file and want to integrate the example into your own configuration, you can instead:

  • Add a component group that instantiates DtCommentLogic in your role definition (for example, a Lua role file under appData/scripts/playerDefinitions).
  • Add the appropriate DtCommentConnector variant to your connection configuration (for example, data/connection/connectionDIS.lua).

Conceptually this looks like:

role.componentGroups["commentLogic"] = {
components = {
{ type = "DtCommentLogic" } -- Frontend UI component
}
}
connection.connectors = {
"DtCommentConnector-DIS", -- For DIS protocol
-- Or "DtCommentConnector-HLA1516E" for HLA1516e
-- Other connectors...
}

Testing procedure

Single-Station Test (verify receive/display behavior):

  1. Launch VR-Engage with the comment plugins loaded.
  2. Select a role that includes DtCommentLogic and DtCommentConnector (for example, the human role from the customPdu simulation model set when running with VR-Forces).
  3. Engage an entity.
  4. Drive an inbound comment using one of these options:
    • Use VR-Forces or another external tool to send a Comment PDU or interaction to your entity.
    • Or add a small test script or temporary UI in VR-Engage that creates and publishes a CommentMessage targeting your own entity, then restart and repeat steps 1–3.
  5. Verify UI behavior:
    • A "Comment" menu appears when a comment is received.
    • Each incoming comment appears as a separate menu element.
    • Clicking the element with action "closeComment" dismisses that comment from the menu.

Multi-Station Test (end-to-end network verification):

  1. Set up the exercise:
    • Start a DIS or HLA exercise.
    • Launch two VR-Engage instances on the same network, both with comment plugins and connectors enabled and roles that include DtCommentLogic.
  2. Provide a sender for CommentMessage:
    • Either add UI or scripting to one station that creates and publishes CommentMessage instances (not implemented in this example),
    • Or have an external system (for example, VR-Forces) send Comment PDUs/interactions to one station that should then be forwarded on to the other.
  3. Verify transmission and reception:
    • Check logs on the sending side for connector initialization and outgoing Comment PDUs.
    • Use a network monitor (e.g., Wireshark) if desired to observe Comment traffic.
    • Confirm that the receiving VR-Engage station shows the Comment menu and displays the expected text.
  4. Verify dismissal behavior:
    • On the receiving station, click each comment entry.
    • Ensure the selected entry is removed and the menu hides when no comments remain.

Verification:

  • Check logs for plugin initialization:
    [COMMENT] Initializing example custom pdu front-end
    [COMMENT] Initializing example custom pdu back-end
  • Check VR-Link logs for Comment PDU transmission/reception
  • Verify menu system integration with action menu manager

Troubleshooting

Message library not found:

  • Symptom: Plugins fail to load with "exampleCommentMessage.dll not found"
  • Cause: Shared library not in PATH or not installed to bin64
  • Solution: Verify exampleCommentMessage.dll in <VR-Engage-Install-Dir>\bin64\

Frontend plugin not loading:

  • Symptom: DtCommentLogic component not available
  • Cause: Plugin not in plugins64 or plugin package misconfigured
  • Solution: Check plugin package file includes correct path, verify DLL exists

Network connector not registering:

  • Symptom: Comment PDUs not transmitted/received
  • Cause: Wrong protocol connector loaded or connector not in connection config
  • Solution: Match connector protocol (DIS vs HLA) with connection type, verify connector in connectors list

Comments not appearing:

  • Symptom: PDUs received but no menu displayed
  • Cause: Entity ID mismatch or menu manager not available
  • Solution: Verify receiver ID matches local entity, check menu manager initialized

Duplicate messages:

  • Symptom: Each comment appears twice or echoes back to sender
  • Cause: Sender/receiver filtering logic broken
  • Solution: Ensure cmsg->getSender() != myPlayer->entityId() check in frontend

Technical reference

File structure

examples/customPdu/
├── README.md # This documentation
├── commentMessage/ # Shared message library
│ ├── CMakeLists.txt # Build configuration
│ ├── comment.lua # Message schema definition
│ ├── export.h # DLL export macros
│ ├── commentMessage.h # (Generated) Message class
│ └── commentMessage.cxx # (Generated) Implementation
├── commentFrontend/ # Frontend UI component
│ ├── CMakeLists.txt # Build configuration
│ ├── plugin.cxx # Plugin entry point
│ ├── commentLogic.h # Component declaration
│ └── commentLogic.cxx # Component implementation
└── commentNet/ # Network connector
├── CMakeLists.txt # Multi-protocol build config
├── plugin.cxx # Plugin entry point
├── commentConnector.h # Connector declaration
└── commentConnector.cxx # Connector implementation

Key classes

Class Base Class Purpose Component
CommentMessage DtVreMessage Internal message type commentMessage (generated)
DtCommentLogic DtPlayerComponent Frontend UI and display commentFrontend
DtCommentConnector DtSimEventConnector Network PDU handling commentNet

API methods used

Message System APIs:

  • DtVreMessageManager::instance() - Get message manager singleton
  • DtVreMessageManager::addHandler() - Register message handler
  • DtVreMessageManager::removeHandler() - Unregister message handler
  • DtVreMessageManager::queueMessage() - Publish message asynchronously
  • DtVreMessageDelegate - Type-safe message handler callback

Action Menu APIs:

  • DtPlayerStation::playerAttributeStore() - Access shared services
  • DtPlayerAttributeStore::getAttribute<>() - Retrieve service by type
  • DtMenuManager::addMenu() - Register custom menu
  • DtMenu::addElement() - Add dynamic menu item
  • DtMenu::removeElement() - Remove menu item
  • DtMenu::closeMenu() - Hide menu
  • DtMenuElement constructor - Create menu item with action and parameter

VR-Link APIs:

  • DtCommentInteraction::addCallback() - Register PDU callback
  • DtCommentInteraction::removeCallback() - Unregister PDU callback
  • DtCommentInteraction::senderId() - Get sender entity ID
  • DtCommentInteraction::receiverId() - Get receiver entity ID
  • DtCommentInteraction::comment() - Get comment text
  • DtCommentInteraction::setSenderId() - Set sender ID
  • DtCommentInteraction::setReceiverId() - Set receiver ID
  • DtCommentInteraction::setComment() - Set comment text
  • DtExerciseConn::sendStamped() - Transmit PDU with timestamp

Message definition schema

Lua Message Attributes:

Attribute Type Description
fileName string Base name for generated files (without extension)
className string C++ class name (will be suffixed with "Message")
messageType string Unique message type identifier (hierarchical dotted notation)
comment string Documentation comment for generated class
attributes table array List of message fields with {type, name}

Supported Attribute Types:

  • Primitive: String, Int, Double, Bool
  • VR-Engage: EntityIdentifier, Vector3, Quaternion
  • Custom: Define additional types in message generator configuration

Comment PDU structure (DIS)

DIS Comment PDU is a freeform text message with:

Field Type Description
Originating Entity ID Entity Identifier Sender entity
Receiving Entity ID Entity Identifier Receiver entity (or 0.0.0 for broadcast)
Variable Datum Datum Record Contains comment text as variable-length string

HLA Comment Interaction has similar fields mapped to HLA object attributes.

Build targets

  • Message Library: commentMessageexampleCommentMessage.dll (SHARED)
  • Frontend Plugin: commentFrontendexampleCommentFrontend.dll (MODULE)
  • Network Plugins:
    • commentDISexampleCommentDIS.dll
    • commentHLA1516eexampleCommentHLA1516e.dll
    • commentHLA4exampleCommentHLA4.dll
  • Install Locations:
    • Message library: bin64/
    • Frontend plugin: plugins64/vrEngage/release/
    • Network plugins: plugins64/vrEngage/release/

Protocol support

This example builds separate network connector plugins for each protocol:

Protocol Preprocessor Define Connector Name Build Target
DIS DtDIS DtCommentConnector-DIS commentDIS
HLA 1516 DtHLA_1516 DtCommentConnector-HLA1516 commentHLA1516
HLA 1516 Evolved DtHLA_1516_EVOLVED DtCommentConnector-HLA1516E commentHLA1516e
HLA 4 DtHLA_4 DtCommentConnector-HLA4 commentHLA4

Only one connector should be loaded per VR-Engage instance (matching the exercise connection protocol).


Related documentation: