VR-Engage  2.2
Loading...
Searching...
No Matches
Inter-Process Communication

VR-Engage operates as a distributed system with separate frontend and backend processes, even when running on a single machine. The frontend handles visualization, user input, and immediate feedback, while the backend manages simulation physics, entity behavior, and network interoperability. This section covers the VR-Engage-specific mechanisms for communication between these processes: the Messaging Framework for structured commands and events, the joystick message pipeline for control input, state properties for backend-to-frontend entity state, and extended data for custom per-entity information.

For information on the Player Attribute Store (frontend component state coordination), see the Player Attribute Store section in the Player Station Framework documentation.

Communication architecture

The frontend and backend processes communicate through a network connection, typically over localhost for single-machine deployments or across the network for distributed configurations. The frontend process (vrEngage.exe) handles input processing, control logic, UI rendering, and the Player Attribute Store for coordinating frontend component state. The backend process (vrEngageSim.exe) runs the simulation: joystick controllers receive input commands, actuators apply control inputs to entity models, the entity model (vreVrfmodel) computes physics and behavior, and the networking layer publishes entity state via DIS or HLA.

This separation provides process isolation that simplifies development and testing. Frontend components can be developed and debugged independently from backend simulation logic. The network boundary also supports distributed deployments where visualization runs on a different machine than simulation, which is common in multi-channel display configurations or instructor-operator station setups.

VR-Engage uses several distinct communication mechanisms, each suited to different data patterns:

Mechanism Direction Purpose Examples
Joystick Messages Frontend → Backend Control input Throttle, steering, weapon commands
State Properties Backend → Frontend Entity state updates Speed, fuel level, damage state
Extended Data Backend → Frontend Custom entity data Role-specific values, subsystem status
Command Messages Frontend → Backend Discrete actions Fire weapon, change mode, task entity
Player Attribute Store Frontend only Frontend component coordination UI state, settings, display preferences

VR-Engage Messaging Framework

The Messaging Framework provides structured, type-safe publish/subscribe communication between components. Messages are strongly-typed C++ classes generated from Lua schema definitions. Each message type has a unique identifier, defined fields with specific data types, and automatic serialization for cross-process delivery.

Message architecture

Messages flow through a central dispatcher (DtVreMessageManager) that routes them to registered subscribers based on message type. The framework handles serialization for cross-process delivery and supports both synchronous local delivery and asynchronous remote delivery.

Messages can flow within a single process or across the frontend/backend boundary. Within the frontend, components exchange messages frequently for coordination: UI state changes, player station lifecycle events, input focus notifications, and overlay visibility updates all use local message delivery. Cross-process messages require serialization and network transport, which the framework handles transparently.

Publishing and subscribing

Components publish messages by creating message instances and passing them to the message manager:

// filepath: src/sensorMonitor.cxx
#include "vreMessages/customSensorAlert.h"
void DtSensorMonitor::publishAlert(int sensorId, float value)
{
auto alert = makVre::CustomSensorAlertMessage::create();
alert->setSensorId(sensorId);
alert->setSeverity(calculateSeverity(value));
alert->setValue(value);
alert->setTimestamp(myPlayerStation->simulationTime());
alert->setSensorName(mySensors[sensorId].name());
}
virtual void queueMessage(DtVreMessage *msg, double delay=0.0)=0
Queues a message for later dispatch.
static DtVreMessageManager & instance()
Gets the singleton instance of the message manager.
Defines the central message manager for the VREngage messaging system.

Components subscribe to message types by registering handler functions:

// filepath: src/alertDisplay.cxx
#include "vreMessages/customSensorAlert.h"
bool DtAlertDisplayComponent::initialize(DtPlayerStation* player, DtInitTable& config)
{
if (!DtPlayerComponent::initialize(player, config))
{
return false;
}
makVre::CustomSensorAlertMessage::theType(),
makVre::DtVreMessageDelegate(this, &DtAlertDisplayComponent::handleSensorAlert));
return true;
}
makVre::DtVreMessageResult DtAlertDisplayComponent::handleSensorAlert(makVre::DtVreMessage* msg)
{
ASSERT_TYPE(msg, makVre::CustomSensorAlertMessage, alertMsg);
AlertInfo alert;
alert.sensorName = alertMsg->getSensorName();
alert.severity = alertMsg->getSeverity();
alert.value = alertMsg->getValue();
myActiveAlerts.push_back(alert);
updateAlertDisplay();
}
Abstract base class for all VREngage messages.
Definition vreMessage.h:50
virtual void addHandler(const std::string &messageType, const DtVreMessageDelegate &handler, HandlerPosition handlerPos=HandlerPosition::BACK)=0
Registers a message handler for a specific message type.
DtDelegate< DtVreMessageResult, DtVreMessage * > DtVreMessageDelegate
Delegate type for message handler callbacks.
Definition vreMessage.h:212
DtVreMessageResult
Enumeration of possible message handling results.
Definition vreMessage.h:33
@ HANDLED
The handler processed the message; continue processing.
Definition vreMessage.h:35
#define ASSERT_TYPE(msg, MsgType, msgCast)
Asserts that a message is of the expected type and casts it.
Definition vreMessageManager.h:205

Handler results and dispatch control

Message handlers return a DtVreMessageResult that controls further dispatch:

Result Meaning
IGNORED Handler did not process the message; continue dispatching to other subscribers
HANDLED Handler processed the message; continue dispatching to other subscribers
EATEN Handler processed the message; stop dispatching (no further subscribers receive it)

Most handlers return HANDLED or IGNORED, allowing multiple components to observe the same message. The EATEN result is used when a component takes exclusive action, such as an input handler consuming a key press that should not propagate to other components.

Intra-process message examples (frontend):

  • Player-station state messages: Notify components when the player station changes state (connecting, role selection, engaged)
  • Input focus messages: Coordinate which component receives keyboard/mouse input
  • Overlay visibility messages: Synchronize HUD and overlay display state across UI components
  • Camera view messages: Propagate observer position changes to interested components

Cross-process message examples:

  • JoystickMessage: Carries control input from frontend control logic to backend controllers
  • VrfSetStatePropertyMessage: Delivers entity state updates from backend actuators to frontend displays
  • VrfSetLocationMessage: Sends location commands from frontend to backend entity
  • FireMessage: Transmits weapon fire events between processes

Message categories

VR-Engage defines messages in Lua schema files organized by functional area. The build system processes these files to generate C++ classes with complete serialization support.

Core message categories include:

  • Input messages (joystick.lua, input.lua): Control input from frontend to backend
  • Entity messages (entity.lua, ownship.lua): Entity state and lifecycle events
  • VRF command messages (vrfEntitySetDataRequests.lua, vrfEntityTasks.lua): Backend entity control
  • Sensor messages (vrfSensors.lua, sensorContacts.lua): Sensor data and updates
  • Session messages (vrfSessionStatus.lua, connection.lua): Connection and session management
  • UI messages (playerStationUI.lua, playerStationState.lua): Frontend state coordination

Defining custom messages

Custom messages are defined in Lua schema files. The schema specifies the message name, type identifier, and field definitions:

-- filepath: libsrc/framework/vreMessages/customMessages.lua
MESSAGE{
fileName = "customSensorAlert";
className = "CustomSensorAlert";
messageType = "custom.sensor.alert";
attributes = {
{type = "int", name = "sensorId"};
{type = "int", name = "severity"};
{type = "float", name = "value"};
{type = "double", name = "timestamp"};
{type = "string", name = "sensorName"};
};
comment = "Alert generated when a sensor threshold is exceeded.";
}

The schema fields are:

Field Purpose
fileName Base name for generated .h and .cpp files
className C++ class name (the generator appends Message suffix)
messageType Hierarchical type string for routing and wildcard matching
attributes List of typed fields with accessor/mutator generation
comment Documentation comment for the generated class

Supported attribute types include primitives (bool, int, Int32, UInt32, float, double, string, String), VR-Engage types (EntityIdentifier, EntityEnum, Vector3d), lists (List<String>), and enumerations defined in the enums block.

Code generation

The generateMessages() CMake function processes Lua schema files and produces C++ source code during the build. The generator creates header and source files with the message class, type-safe accessors, serialization methods, and factory registration code. Generated classes automatically register with DtVreMessageFactory when the containing library loads, allowing the message manager to instantiate and route messages correctly.

For each Lua message definition, the generator produces:

  • A header file declaring the message class with accessor methods
  • A source file implementing serialization, deserialization, and factory registration
  • Type-safe get and set methods for each attribute
  • A static create() factory method and theType() type accessor

For detailed instructions on integrating message generation into your CMake build, including the generateMessages() function parameters and library linking requirements, see the Environment Setup & Build Guide. The Custom PDU and Entity Detection examples demonstrate complete message definition and generation workflows.

The code generator produces C++ classes with type-safe accessors for each field:

// filepath: include/framework/vreMessages/customSensorAlert.h (auto-generated)
namespace makVre
{
constexpr const char* CustomSensorAlertMessageType = "custom.sensor.alert";
class VREMESSAGES_DLL CustomSensorAlertMessage : public makVre::DtVreMessage
{
public:
struct CustomSensorAlertData
{
int mySensorId;
int mySeverity;
float myValue;
double myTimestamp;
std::string mySensorName;
};
static CustomSensorAlertMessage* create();
static CustomSensorAlertMessage* createFromData(const CustomSensorAlertData data);
virtual int getSensorId() const;
virtual void setSensorId(int val);
virtual int getSeverity() const;
virtual void setSeverity(int val);
// ... additional accessors
};
} // namespace makVre
#define VREMESSAGES_DLL
Export/import macro for non-Windows platforms.
Definition export.h:39
Include export definitions for this library.
Definition glsVreMessageUtil.h:49

Hierarchical message types and wildcard subscriptions

Message types use dot-notation hierarchies that group related messages and support wildcard subscriptions. A message type like entity.extendedState.request has three levels: the category (entity), subcategory (extendedState), and specific message (request). This structure allows components to subscribe to messages at any level of specificity.

Wildcard subscriptions use * to match any value at a specific hierarchy level. A subscription to entity.* receives all messages in the entity category, regardless of subcategory or specific type. This is useful for components that need to monitor a broad category of events, such as a logging component that captures all entity-related messages or a debugging tool that tracks all sensor updates.

Examples of hierarchical message types:

Message Type Category Purpose
system.input.joystick Input Joystick control commands
entity.discovered Entity Entity first seen on network
entity.state.bool Entity Boolean state property updates
vrf.set.entity.location VRF Set entity location commands
simulation.fire Simulation Weapon fire events

Wildcard subscription examples:

Pattern Matches
system.input.* All input messages (joystick, etc.)
entity.* All entity messages (discovered, realized, removed, state, etc.)
vrf.set.* All VR-Forces set-data messages
simulation.* All simulation messages (fire, state, etc.)

Subscribing with a wildcard:

// filepath: src/messageLogger.cxx
makVre::DtVreMessageResult DtMessageLogger::handleEntityMessage(makVre::DtVreMessage* msg)
{
logMessage(msg->type(), msg);
}
void DtMessageLogger::initialize()
{
// Subscribe to all entity messages using wildcard
makVre::DtVreMessageDelegate(this, &DtMessageLogger::handleEntityMessage));
}
virtual const std::string & type() const =0
Gets the string name of the message type.
@ IGNORED
The handler did not process the message; continue processing.
Definition vreMessage.h:34

The DtVreMessageId class handles type parsing and matching. When a message is published, the dispatcher compares its type against all registered subscriptions, including wildcard patterns, and invokes matching handlers in registration order.

Data exchange mechanisms

VR-Engage provides several specialized mechanisms for exchanging data between frontend and backend. Each is optimized for particular data patterns and use cases.

Joystick message pipeline

The joystick message system provides the primary communication path from frontend input devices to backend actuators. When a player operates a control (joystick, keyboard, gamepad), the frontend translates this input into a JoystickMessage that crosses the process boundary to the backend.

This section covers the message passing aspects of joystick input. For backend processing—how the VR-Forces joystick infrastructure routes messages to controllers and actuators—see the Joystick controller section in VR-Forces Integration.

Message flow overview

The frontend creates joystick messages in response to mapped input actions. These messages travel through the message manager to the backend, where DtVrfRemoteControlConnector receives them and routes them into the VR-Forces joystick system.

sequenceDiagram
    participant Device as Input Device
    participant Input as DtInputLogic
    participant Control as Control Logic
    participant MsgMgr as DtVreMessageManager
    participant Remote as DtVrfRemoteControlConnector
    participant VRF as VR-Forces Joystick System

    Device->>Input: Raw Input Event
    Input->>Control: Action Callback
    Control->>Control: Create JoystickMessage
    Control->>MsgMgr: queueMessage()
    MsgMgr->>Remote: Deliver Message
    Remote->>VRF: Route to Controller

JoystickMessage structure

The JoystickMessage carries control input from frontend to backend:

// filepath: include/framework/vreMessages/joystick.h (auto-generated)
namespace makVre
{
constexpr const char* JoystickMessageType = "system.input.joystick";
class VREMESSAGES_DLL JoystickMessage : public makVre::DtVreMessage
{
public:
// Factory methods
static JoystickMessage* create();
// Entity identification
virtual const DtEntityIdentifier& getEntityId() const;
virtual void setEntityId(const DtEntityIdentifier& val);
// Function routing
virtual const std::string& getFunctionGroup() const; // e.g., "driver", "gunner"
virtual void setFunctionGroup(const std::string& val);
virtual const std::string& getFunction() const; // e.g., "throttle", "steering"
virtual void setFunction(const std::string& val);
// Control value
virtual double getValue() const; // Typically -1.0 to 1.0
virtual void setValue(double val);
// Repeat flag for continuous actions
virtual bool getRepeat() const;
virtual void setRepeat(bool val);
};
} // namespace makVre
constexpr const char * JoystickMessageType
Definition joystick.h:27
Field Purpose Example Values
entityId Target entity for the control input Entity identifier of controlled vehicle
functionGroup Controller group that handles this input "driver", "gunner", "pilot"
function Specific control function name "throttle", "steering", "fire"
value Control value, typically normalized -1.0 to 1.0 for axes, 0.0 or 1.0 for buttons
repeat Whether the action should repeat true for held buttons, false for toggle actions

Sending joystick messages from control logic

Frontend control logic components convert input actions to joystick messages. The pattern involves initializing input logic, registering action handlers, caching the function group from role configuration, and creating messages in response to actions.

// filepath: examples/vehicleBlinker/vehicleBlinkerFrontend/vehicleBlinkerControlLogic.cxx
using namespace makVre;
{
{
return false;
}
if (myJoystickFunctionGroups.size() > 0)
{
}
else
{
LOG_WARN("VRE") << "Need to have at least 1 joystick control group" << std::endl;
return false;
}
myInputLogic.initialize();
const std::string inputGroup = "lights";
myInputLogic.loadInputConfig(myInputConfigFile, inputGroup);
myInputLogic.addActionHandler(inputGroup,
"left-blinker",
myInputLogic.addActionHandler(inputGroup,
"right-blinker",
return true;
}
{
sendJoystickMessage("LeftBlinker", val);
}
{
sendJoystickMessage("RightBlinker", val);
}
void DtVehicleBlinkerControlLogic::sendJoystickMessage(const std::string& function, double val, double delay)
{
JoystickMessage* msg = JoystickMessage::create();
msg->setEntityId(DtPlayerComponent::player().entityId());
msg->setFunction(function);
msg->setValue(val);
DtVreMessageManager::instance().queueMessage(msg, delay);
}
virtual void setRightBlinker(double val)
virtual void setLeftBlinker(double val)
virtual bool initialize(makVre::DtPlayerStation *player, makVre::DtInitTable &config) override
Initializes the entity control logic.
std::string myMainJoystickGroup
Definition vehicleBlinkerControlLogic.h:59
makVre::DtInputLogic myInputLogic
Definition vehicleBlinkerControlLogic.h:51
virtual void sendJoystickMessage(const std::string &function, double val, double delay=0)
std::string myInputConfigFile
Name of the input configuration file.
Definition entityControlLogic.h:256
virtual bool initialize(DtPlayerStation *player, DtInitTable &config) override
Initializes the entity control logic.
std::vector< std::string > myJoystickFunctionGroups
List of joystick function groups used by this controller.
Definition entityControlLogic.h:246
Table-based access to Lua state for configuration data.
Definition initializer.h:38
virtual DtPlayerStation & player()
Gets the player station.
Class for managing the user interface for engaged roles.
Definition playerStation.h:51
virtual void setFunction(const std::string &val)
virtual void setValue(double val)
virtual void setFunctionGroup(const std::string &val)
virtual void setEntityId(const DtEntityIdentifier &val)
#define LOG_WARN(channel)
Macro to log a warning message to log files.
Definition logger.h:69
DtDelegate< void, float > DtActionDelegate
Delegate type for handling action events.
Definition vreInputManager.h:66

Backend message receipt

The DtVrfRemoteControlConnector class receives joystick messages on the backend. When a JoystickMessage arrives, the connector extracts the message parameters and routes them into the VR-Forces joystick system:

// filepath: libsrc/framework/vrePlayerStationConnector/vrfRemoteControlConnector.cxx
{
JoystickMessage* joystickMessage = dynamic_cast<JoystickMessage*>(message);
if (joystickMessage && myVrfRemoteController)
{
myVrfRemoteController->sendJoystickControl(lookupUUID(joystickMessage->getEntityId()),
joystickMessage->getFunctionGroup(), joystickMessage->getFunction(), joystickMessage->getValue());
return HANDLED;
}
return IGNORED;
}
virtual DtVreMessageResult handleJoystick(DtVreMessage *msg)
Entity manipulation message handlers.
Definition joystick.h:30
virtual const std::string & getFunction() const
virtual double getValue() const
virtual const DtEntityIdentifier & getEntityId() const
virtual const std::string & getFunctionGroup() const

The message manager delivers joystick messages to DtVrfRemoteControlConnector, which then uses the VR-Forces joystick source infrastructure to route the input to the appropriate controller component. For details on how controllers process these inputs and update actuator ports, see the Joystick controller section in VR-Forces Integration.

Helper utilities

The glsVreMessageUtil.h header provides convenience functions for common joystick operations:

// filepath: include/commonRoleLibraries/glsUtil/glsVreMessageUtil.h
namespace makVre
{
//! Send a joystick message to control an entity.
static inline void sendJoystickMessage(
const DtEntityIdentifier& entity,
const std::string& functionGroup,
const std::string& function,
double value,
bool repeat = false)
{
msg->setEntityId(entity);
msg->setFunctionGroup(functionGroup);
msg->setFunction(function);
msg->setValue(value);
msg->setRepeat(repeat);
}
} // namespace makVre
virtual void setRepeat(bool val)
static JoystickMessage * create()
static void sendJoystickMessage(const DtEntityIdentifier &entity, const std::string &functionGroup, const std::string &function, double value, bool repeat=false)
Send a joystick message to the ownship entity.
Definition glsVreMessageUtil.h:51

State properties

State properties provide a mechanism for the backend to publish entity state values that the frontend can access. Unlike the Player Attribute Store (which is frontend-centric), state properties originate from backend simulation components and flow to the frontend for display in UI elements.

State property flow

sequenceDiagram
    participant Sim as Simulation Component
    participant Model as vreVrfmodel
    participant Prop as State Property
    participant Msg as VrfSetStatePropertyMessage
    participant Frontend as Frontend Component
    participant UI as QML UI

    Sim->>Model: Update entity state
    Model->>Prop: Set property value
    Prop->>Msg: Create update message
    Msg->>Frontend: Deliver via connector
    Frontend->>UI: Update QML property

VrfSetStatePropertyMessage

The VrfSetStatePropertyMessage carries state property updates from backend to frontend:

// filepath: include/framework/vreMessages/vrfSetStateProperty.h (auto-generated)
namespace makVre
{
constexpr const char* VrfSetStatePropertyMessageType = "vrf.set.entity.stateProperty";
{
public:
enum PropertyType { PropertyType_String, PropertyType_Bool, PropertyType_Int, PropertyType_Real };
struct VrfSetStatePropertyData
{
DtEntityIdentifier myEntityId;
PropertyType myType;
std::string myName;
std::string myValue;
};
// Accessors
virtual const DtEntityIdentifier& getEntityId() const;
virtual PropertyType getType() const;
virtual const std::string& getName() const;
virtual const std::string& getValue() const;
};
} // namespace makVre
Definition vrfSetStateProperty.h:28
constexpr const char * VrfSetStatePropertyMessageType
Definition vrfSetStateProperty.h:25

Using state properties in QML

State properties are commonly used to drive HUD and dashboard displays. The frontend connects state properties to QML object properties through the overlay connection system:

// filepath: data/UI/HUDS/examples/vehicleBlinker/dashboard-with-blinkers.qml
Item {
id: state
objectName: "state"
// Properties updated from backend state properties
property bool leftBlinkerActive: false
property bool rightBlinkerActive: true
property real speed: 0
property real rpm: 0
property real fuel: 1
property real temperature: 0.6
property bool start: true
property string gear: "N"
}
// Use properties in UI elements
Text {
text: Math.round(state.speed) + " km/h"
visible: state.start
}

The role configuration maps state properties to QML properties using the bindQmlPropertyToAttribute table:

// filepath: data/simulationModelSets/examples/vehicleBlinker/roles/driver-with-blinkers.lua
bindQmlPropertyToAttribute = {
speed = "ownship.speed";
rpm = "rpm";
gear = "set-gear";
fuel = "fuel";
fuelFullAmount = "fuelFullAmount";
engineMaxRpm = "engine-max-rpm";
leftBlinkerActive = "left-blinker-active";
rightBlinkerActive = "right-blinker-active";
};

Publishing state properties from backend

Backend actuator components publish state properties to communicate state changes to the frontend:

// filepath: examples/vehicleBlinker/vehicleBlinkerSim/vehicleBlinkerActuator.cxx
{
if (!DtActuatorComponent::init())
{
return false;
}
myLeftBlinkerActive = entity()->nextFrameStateProperties().findProperty<DtRwBoolean>("left-blinker-active");
myRightBlinkerActive = entity()->nextFrameStateProperties().findProperty<DtRwBoolean>("right-blinker-active");
return true;
}
{
if (myLeftBlinkerPort->activeConnection() && myLeftBlinkerPort->newData())
{
myLeftBlinkerPort->dataReceived();
if (myLeftBlinkerPort->value() == true)
{
myLeftBlinkerActive->setValue(!myLeftBlinkerActive->value());
updateLeftBlinker();
}
}
if (myRightBlinkerPort->activeConnection() && myRightBlinkerPort->newData())
{
myRightBlinkerPort->dataReceived();
if (myRightBlinkerPort->value() == true)
{
myRightBlinkerActive->setValue(!myRightBlinkerActive->value());
updateRightBlinker();
}
}
}
virtual bool init() override
virtual void tick() override

Extended data

Extended data provides a mechanism for transmitting custom per-entity data that extends beyond standard DIS/HLA entity state. This is useful for VR-Engage-specific entity properties that need to be shared between frontend and backend or with other VR-Engage instances.

Extended state connector

The DtExtendedStateConnector manages access to extended state properties:

// filepath: include/framework/vrePlayerStationConnector/extendedStateConnector.h
namespace makVre
{
//! Connector for handling extended state properties of simulation entities.
//! Provides mechanisms to request specific property values, monitor entity states,
//! and convert between different data types.
{
public:
virtual ~DtExtendedStateConnector() override;
virtual void install(
DtPlayerStationApp* app, makVrv::DT_PROTOCOL_NAMESPACE::DtVrlinkConnection* connection) override;
virtual void tick() override;
virtual void shutdown() override;
protected:
template <typename RwType, typename ValueType, typename MessageType>
bool sendStatePropertyMessage(DtReflectedExtEntity* entity, const std::string& name);
virtual void sendExtendedDataMessage(
DtReflectedExtEntity* entity,
const std::string& name,
const std::string& type);
virtual bool sendStatePropertyMessage(
DtReflectedExtEntity* entity,
const std::string& name,
const std::string& type);
};
} // namespace makVre
Base class for all simulation event connectors in VREngage.
Definition simEventConnector.h:54
Connector for handling extended state properties of simulation entities.
Definition extendedStateConnector.h:38
Top-level class representing the VR-Engage application.
Definition playerStationApp.h:163
#define VRL_DLL
Empty definition for non-Windows platforms.
Definition export.h:52

Supported property types

Extended data supports several property types:

Type C++ Type Use Case
bool bool Flags and toggles
int int32_t Counts, indices, enumerations
float float Measurements, ratios
string std::string Text data, identifiers
ammoClipMap Custom Ammunition clip configurations

Design guidance

This section covers patterns and practices for effective IPC design, including mechanism selection, performance optimization, and debugging techniques.

Choosing the right mechanism

Selecting between messaging and attributes depends on the nature of the data and its usage pattern. Each mechanism has strengths suited to different scenarios.

Use messaging for events and commands

Messages are appropriate for discrete occurrences that happen at specific moments:

  • Commands: "Fire weapon", "Eject crew", "Activate countermeasures"
  • Events: "Damage received", "Target acquired", "Waypoint reached"
  • Notifications: "Fuel critical", "Engine failure", "Mission complete"

Messages carry context about the event (what happened, when, to whom) and trigger immediate responses from subscribers. They don't persist after delivery—if a component isn't subscribed when a message arrives, it won't receive that message.

Use attributes for persistent state

Attributes are appropriate for values that persist over time and can be queried at any moment:

  • Continuous values: Speed, heading, fuel level, ammunition count
  • Configuration: Weapon loadout, sensor settings, control preferences
  • Status flags: Engine running, landing gear deployed, autopilot engaged

Attributes maintain their values until explicitly changed. New components can read current state immediately upon initialization without waiting for update messages.

Decision matrix

Characteristic Messaging Attribute Store
Persistence Transient (event-based) Persistent (state-based)
Query pattern Push (subscription) Pull (query) or Push (callbacks)
Timing Discrete moments Continuous availability
Late subscribers Miss past events See current state
Data volume Any size Best for simple values
Update frequency Variable Regular synchronization

Hybrid patterns

Many features benefit from combining both mechanisms. A weapon system might use attributes for ammunition count (persistent state) and messages for fire commands (discrete events).

// filepath: src/weaponSystem.cxx
void DtWeaponSystem::fireWeapon()
{
// Check state from attributes
DtPlayerAttributeStore& attrs = playerStation()->playerAttributeStore();
int ammo = attrs.getAttribute<int>("systems/weapons/ammoCount");
bool armed = attrs.getAttribute<bool>("systems/weapons/armed");
if (ammo <= 0 || !armed)
{
return; // Can't fire
}
// Update state in attributes
attrs.setAttribute<int>("systems/weapons/ammoCount", ammo - 1);
// Send fire command via joystick message
fireMsg->setEntityId(myEntityId);
fireMsg->setFunctionGroup(myWeaponGroup);
fireMsg->setFunction("fire");
fireMsg->setValue(1.0);
fireMsg->setRepeat(false);
}

Performance considerations

Efficient IPC is critical for maintaining high frame rates and responsive simulation. Both mechanisms have performance characteristics that influence design decisions.

Message performance

Message overhead includes serialization, network transmission, and deserialization. For high-frequency messages, consider:

  • Batching: Combine multiple related updates into single messages
  • Filtering: Use subscriber filters to reduce callback invocations
  • Pooling: Reuse message objects to reduce allocation overhead
// filepath: src/batchedSensorPublisher.cxx
void DtBatchedSensorPublisher::publishSensorReadings()
{
// Instead of publishing one message per sensor per frame,
// batch all sensor readings into a single message.
// Define a custom batch message type in your Lua schema.
auto batch = makVre::SensorBatchMessage::create();
for (const auto& sensor : mySensors)
{
batch->addSensorId(sensor.id());
batch->addValue(sensor.currentValue());
}
// Single message for all sensors
}

Attribute performance

Attribute store overhead includes path resolution, value storage, and synchronization. For optimal performance:

  • Use handles: Cache attribute handles for frequently-accessed values
  • Minimize writes: Only update attributes when values actually change
  • Batch updates: Group related attribute changes to reduce sync overhead
// filepath: src/efficientStateUpdater.cxx
void DtEfficientStateUpdater::updateState(double newSpeed, double newHeading)
{
// Only update if values have changed significantly
constexpr double speedThreshold = 0.1;
constexpr double headingThreshold = 0.01;
bool speedChanged = std::abs(newSpeed - myLastPublishedSpeed) > speedThreshold;
bool headingChanged = std::abs(newHeading - myLastPublishedHeading) > headingThreshold;
if (speedChanged || headingChanged)
{
// Batch updates by modifying multiple attributes before sync
if (speedChanged)
{
mySpeedHandle.set(newSpeed);
myLastPublishedSpeed = newSpeed;
}
if (headingChanged)
{
myHeadingHandle.set(newHeading);
myLastPublishedHeading = newHeading;
}
}
}

Synchronization tuning

The attribute synchronization rate balances responsiveness against network overhead. Higher rates provide more immediate state updates but increase bandwidth consumption. Configure the rate based on application requirements:

-- filepath: data/config/ipcSettings.lua
ipcSettings = {
attributeSyncRate = 60, -- Sync 60 times per second
messageBatchWindow = 0.016, -- Batch messages within 16ms windows
compressionEnabled = true, -- Compress large updates
deltaCompression = true -- Only send changed values
}

Debugging IPC issues

Diagnosing communication problems requires visibility into message flow and attribute state. VR-Engage provides logging and diagnostic tools for troubleshooting.

Message tracing

Enable message tracing to log all published and received messages:

-- filepath: data/config/debugSettings.lua
debug = {
traceMessages = true,
messageLogLevel = "verbose",
logMessagePayloads = true
}

With tracing enabled, the log shows message flow:

[12:34:56.789] MSG PUBLISH: DtCustomSensorAlertMessage id=31001
sensorId=5 severity=2 value=95.3
[12:34:56.790] MSG DELIVER: DtCustomSensorAlertMessage -> DtAlertDisplayComponent
[12:34:56.790] MSG DELIVER: DtCustomSensorAlertMessage -> DtLoggerComponent

Attribute monitoring

Monitor attribute changes through logging or the diagnostic overlay:

// filepath: src/attributeMonitor.cxx
void DtAttributeMonitor::startMonitoring(const std::string& pathPrefix)
{
DtPlayerAttributeStore& attrs = playerStation()->playerAttributeStore();
myMonitorCallback = attrs.addChangeCallback(
pathPrefix,
[pathPrefix](const std::string& path, const DtAttributeValue& value)
{
LOG_DEBUG("AttrMonitor") << "CHANGE: " << path
<< " = " << value.toString() << std::endl;
},
true // Monitor entire subtree
);
}
#define LOG_DEBUG(channel)
Macro to log a debug message to log files.
Definition logger.h:84

Common issues

Message not received: Verify subscription is registered before messages are published. Check that message types match exactly between publisher and subscriber. Ensure the message manager is properly initialized.

Attribute not synchronized: Confirm both processes are connected and the sync layer is running. Check that attribute paths match exactly (paths are case-sensitive). Verify the attribute exists before reading.

High latency: Check network configuration for localhost vs. remote connections. Review message rates and consider batching. Monitor CPU usage on both processes for bottlenecks.

State oscillation: Indicates both processes are writing the same attribute. Designate one process as authoritative for each attribute to prevent conflicts.

Best practices

Effective IPC design follows patterns that ensure reliability, performance, and maintainability.

Design for failure: Network connections can fail or experience delays. Components should handle missing messages and stale attributes gracefully. Use timeouts and fallback values where appropriate.

Minimize coupling: Components should communicate through well-defined message and attribute interfaces rather than direct references. This enables independent testing and deployment.

Document contracts: Clearly document which component owns each attribute and which messages each component publishes or subscribes to. This prevents conflicts and simplifies debugging.

Version messages carefully: When message formats change, consider backward compatibility. Adding optional fields is safer than modifying existing fields.

Test across processes: IPC behavior can differ between same-process and cross-process scenarios. Test with actual distributed deployment to catch serialization and timing issues.

Networking and Protocols (DIS/HLA)

VR-Engage achieves network interoperability with external simulators through VR-Link, MAK's middleware library that abstracts the differences between DIS (Distributed Interactive Simulation) and HLA (High Level Architecture) protocols. The backend process handles all network publishing and subscribing through VR-Forces and VR-Link.

Protocol overview

Protocol Transport Use Case Key Characteristics
DIS UDP Multicast Real-time tactical training Connectionless, low latency, standardized PDUs
HLA RTI Connection Large-scale federation Federation management, time synchronization, FOM-based

VR-Link abstraction

VR-Link provides protocol-agnostic APIs that work identically across DIS and HLA. These examples show VR-Link API patterns; for complete API documentation, refer to the VR-Link Developer's Guide:

// Protocol-agnostic entity state access (VR-Link API)
DtReflectedEntity* entity = conn->reflectedEntityList()->find(entityId);
DtVector3 position = entity->location();
DtEulerAngles orientation = entity->orientation();
// Protocol-agnostic interaction sending (VR-Link API)
DtWeaponFireInteraction fire;
fire.setFiringEntityId(myEntityId);
fire.setTargetEntityId(targetId);
conn->sendStamped(fire);

The backend automatically publishes standard entity state (position, orientation, velocity, appearance) through the dead reckoning infrastructure. Custom data can be transmitted via articulated parts, comment interactions, or VR-Engage's extended data mechanism.

Exercise configuration

Network parameters are configured in exercise files:

-- DIS configuration
network = {
protocol = "DIS",
multicastAddress = "239.1.2.3",
port = 3000,
siteId = 1,
applicationId = 1
}
-- HLA configuration
network = {
protocol = "HLA1516e",
federationName = "TrainingFederation",
federateName = "VREngage_Player1",
rtiAddress = "localhost"
}

Further reading

For comprehensive DIS/HLA networking documentation, see:

  • VR-Link User Guide: Protocol abstraction APIs, entity publishing, interaction handling
  • VR-Forces Developer Guide: Backend networking architecture, CGF entity management
  • DIS Standard (IEEE 1278): PDU formats, entity type enumerations, dead reckoning algorithms
  • HLA Standard (IEEE 1516): Federation management, FOM development, time management

See also