VR-Engage's extensibility is built on a flexible plugin architecture that supports two primary extension types, with additional options for specialized scenarios.
Plugin Type Overview
Primary Plugin Types
VR-Engage supports two primary extension types: Frontend plugins that extend the player station UI and controls, and Simulation plugins that add backend entity behaviors and physics. Some scenarios may also use pure VR-Vantage plugins for graphics-only extensions or pure VR-Forces plugins for simulation-only functionality.
Frontend Plugin Architecture
Entry Point
Frontend plugins use the initPlayerStationModule entry point:
{
return true;
}
Top-level class representing the VR-Engage application.
Definition playerStationApp.h:163
VRECOMMONCOMPONENTS_DLL bool initPlayerStationModule(makVre::DtPlayerStationApp *app)
Module initialization function declarations using C linkage.
Defines the DtPlayerStationApp class for the VR-Engage application.
VR-Engage automatically discovers and loads all plugins in the plugins64/vrEngage/release/ directory at startup. No plugin manifest or configuration file is required for frontend plugins—deploy the DLL to the correct directory and it will be loaded.
Registration Patterns
Frontend plugins register their extensions with VR-Engage's factory system during initialization. The factory pattern allows plugins to register creator functions that VR-Engage calls later when it needs to instantiate components. Common registrations include player components (custom UI elements, input handlers, control logic) and message handlers for the publish/subscribe system.
Component Registration:
app->componentFactory().addCreator<DtMyComponent>(DtMyComponentType);
Message handling is typically done within component classes rather than in the plugin entry point. Components register handlers during their initialize() method:
bool DtMyComponent::initialize(DtPlayerStation* player, DtInitTable& config)
{
if (!DtPlayerComponent::initialize(player, config))
{
return false;
}
this, &DtMyComponent::handlePlayerCreated);
return true;
}
static DtVreMessageManager & instance()
Gets the singleton instance of the message manager.
virtual void addHandler(const std::string &messageType, const DtVreMessageDelegate &handler, HandlerPosition handlerPos=HandlerPosition::BACK)=0
Registers a message handler for a specific message type.
Frontend Plugin Examples
Simulation Plugin Architecture
Simulation plugins are VR-Forces plugins that extend the backend simulation engine. They follow VR-Forces plugin conventions and use VR-Forces entry points. For comprehensive VR-Forces plugin development documentation, see the VR-Forces Developer's Guide.
Plugin Configuration
Unlike frontend plugins which are automatically discovered, simulation plugins require explicit configuration to load. VR-Forces uses XML configuration files in the appData/plugins/ directory to specify which plugins to load. Each plugin has a corresponding XML file that identifies the DLL location and loading preferences.
<plugin name="mySimPlugin"
file="plugins64/vrForces/release/mySimPlugin.dll"
myLoad="1"/>
The VR-Engage toolkit installer automatically installs plugin configuration files for the example simulation plugins. For custom plugins, you must create a corresponding XML configuration file or configure plugin loading through the VR-Forces GUI Plugins dialog. For details on plugin configuration and management, see "Managing Plug-ins" in the VR-Forces User's Guide.
Entry Points
Simulation plugins use multiple VR-Forces entry points for different lifecycle phases:
#include <vrfcgf/vrfPluginExtension.h>
#include <vrfcgf/cgf.h>
extern "C"
{
DT_VRF_DLL_PLUGIN void DtPluginInformation(DtVrfPluginInformation& info)
{
info.pluginName = "My Simulation Plugin";
info.pluginVersion = DtVreVersionNumber;
info.pluginCreator = "Your Organization";
info.pluginCreatorEmail = "support@yourorg.com";
}
DT_VRF_DLL_PLUGIN bool DtInitializeVrfPlugin(DtCgf* cgf)
{
DtFactoryManager* factoryManager = cgf->factoryManager();
factoryManager->componentFactory()->addCreatorFcn(
DtMyActuatorType, DtMyActuator::creator);
factoryManager->componentDescriptorFactory()->addCreatorFcn(
DtMyActuatorDescriptorType, DtMyActuatorDescriptor::creator);
return true;
}
DT_VRF_DLL_PLUGIN bool DtPostInitializeVrfPlugin(DtCgf* cgf)
{
return true;
}
DT_VRF_DLL_PLUGIN void DtUnloadVrfPlugin()
{
}
}
The plugin metadata provided in DtPluginInformation() is logged to vrfSim.log when the plugin loads successfully. This information helps verify that your plugin loaded correctly and assists with troubleshooting plugin loading issues.
Registration Patterns
Component Factory Registration:
factoryManager->componentFactory()->addCreatorFcn(
static DtSimComponent * creator(const DtString &name, DtLocalObject *owner, DtSimulationServices *simManager, DtComponentDescriptor *desc=nullptr, DtReaderWriterRegistry *parentRegistry=nullptr)
constexpr char DtVehicleBlinkerActuatorType[]
Definition vehicleBlinkerActuator.h:22
Descriptor Factory Registration:
factoryManager->componentDescriptorFactory()->addCreatorFcn(
makVre::DtVehicleBlinkerActuatorDescriptorType,
makVre::DtVehicleBlinkerActuatorDescriptor::creator);
Simulation Plugin Examples
Shared Libraries
Purpose
Shared libraries contain code used by both frontend and simulation plugins. Common use cases include message definitions (generated from Lua schemas), utility functions, protocol implementations, and common data structures. By placing shared code in a separate library, you avoid duplicating code between frontend and simulation plugins and ensure both sides use identical definitions.
Symbol Export
Shared libraries must export symbols properly for use by other DLLs. This requires an export header that defines platform-specific export/import macros:
#pragma once
#ifdef _WIN32
#ifdef BUILDING_MY_SHARED_LIB
#define MY_SHARED_LIB_EXPORT __declspec(dllexport)
#else
#define MY_SHARED_LIB_EXPORT __declspec(dllimport)
#endif
#else
#define MY_SHARED_LIB_EXPORT
#endif
Class Declaration
Classes in the shared library use the export macro in their declaration:
#pragma once
#include "mySharedLibExport.h"
#include <vreMessages/vreMessage.h>
{
class MY_SHARED_LIB_EXPORT DtMySharedMessage : public DtVreMessage
{
public:
DtMySharedMessage();
virtual ~DtMySharedMessage();
int value() const { return myValue; }
void setValue(int value) { myValue = value; }
private:
int myValue;
};
}
Include export definitions for this library.
Definition glsVreMessageUtil.h:49
CMake Configuration
The CMake configuration defines the export macro when building the library:
# filepath: CMakeLists.txt
add_library(mySharedLib SHARED
mySharedMessage.cxx
utilities.cxx
)
# Define export macro when building this library
target_compile_definitions(mySharedLib PRIVATE BUILDING_MY_SHARED_LIB)
# Consumers link against the library and get the import definition automatically
target_include_directories(mySharedLib PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<INSTALL_INTERFACE:include>
)
Linking from Plugins
Both frontend and simulation plugins link against the shared library:
# Frontend plugin
target_link_libraries(myFrontendPlugin PRIVATE
mySharedLib
vrePlayerStation
)
# Simulation plugin
target_link_libraries(mySimPlugin PRIVATE
mySharedLib
vrfcgf
)
Deployment
Shared libraries are deployed to the bin64/ directory where both frontend and backend processes can find them:
<VR-Engage-Installation>/bin64/mySharedLib.dll
Example
See the customPdu example for a complete implementation. The commentMessage sub-project demonstrates a shared library containing message definitions used by both the frontend (commentFrontend) and network (commentNet) components.
Plugin Lifecycle
Frontend Plugin Lifecycle
- Discovery: VR-Engage scans
plugins64/vrEngage/release/ at startup
- Load: Plugin DLL loaded,
initPlayerStationModule() called
- Registration: Plugin registers components, message handlers, UI elements
- Runtime: Registered components instantiated as needed by player stations
- Shutdown: Automatic cleanup when VR-Engage closes
Simulation Plugin Lifecycle
- Configuration: VR-Forces reads XML files in
appData/plugins/ to determine which plugins to load
- Metadata:
DtPluginInformation() called to retrieve plugin info
- Initialize:
DtInitializeVrfPlugin() called to register factories
- Post-Initialize:
DtPostInitializeVrfPlugin() called after all plugins loaded
- Runtime: Registered components created as entities require them
- Unload:
DtUnloadVrfPlugin() called on shutdown
Plugin Dependencies
Declaring Dependencies
Frontend Plugin Dependencies:
target_link_libraries(myFrontendPlugin PRIVATE
vrePlayerStation # Player station framework
vreMessageManager # Message system
vreMessages # Standard messages
vrvCore # VR-Vantage core
Qt5::Core # Qt framework (if needed)
)
Simulation Plugin Dependencies:
target_link_libraries(mySimPlugin PRIVATE
vrfcgf # VR-Forces CGF framework
vreVrfobjcore # VRE simulation objects
vreVrfmodel # VRE VRF model extensions
)
Cross-Plugin Communication
Plugins communicate through:
- Message System: Type-safe publish/subscribe messaging (
DtVreMessageManager)
- Shared State: Player attribute store for frontend, entity state for simulation
- Network: DIS/HLA PDUs for frontend-to-simulation communication
Plugin Deployment
Installation Locations
Frontend plugins:
<VR-Engage-Installation>/plugins64/vrEngage/release/myPlugin.dll
Simulation plugins:
<VR-Engage-Installation>/plugins64/vrForces/release/mySimPlugin.dll
Shared libraries:
<VR-Engage-Installation>/bin64/mySharedLib.dll
Automatic Installation with CMake
# Automatic installation to correct location
if(FRONTEND_PLUGIN)
install(TARGETS myPlugin DESTINATION plugins64/vrEngage)
elseif(SIMULATION_PLUGIN)
install(TARGETS mySimPlugin DESTINATION plugins64/vrForces)
else()
install(TARGETS myLib DESTINATION bin64)
endif()
Best Practices
Plugin Design
- Minimal Entry Points: Keep plugin entry functions focused on registration only
- Factory Pattern: Use factory registration for component creation, not direct instantiation
- Explicit Dependencies: Link only required libraries to minimize load time
- Error Handling: Return
false from entry points on initialization failure
Component Design
- Single Responsibility: Each component should have one clear purpose
- Lifecycle Awareness: Implement proper initialization and cleanup
- Message Communication: Prefer messaging over direct coupling
- Configuration: Use role parameters for configurable behavior
Performance
- Lazy Loading: Register with factories, not pre-created instances
- Minimal Startup Work: Defer heavy initialization until components are actually used
Troubleshooting
Plugin Not Loading
Check:
- Plugin is in correct directory (
plugins64/vrEngage/release/ or plugins64/vrForces/release/)
- DLL exports required entry point symbol (
initPlayerStationModule or DtInitializeVrfPlugin)
- All dependencies are available (check with Dependency Walker)
- Plugin returns
true from entry point (check VR-Engage logs)
Component Not Registered
Verify:
- Factory registration occurs in plugin entry point before returning
true
- Component type identifier is unique (not conflicting with existing components)
- Creator function signature matches expected pattern
Crashes on Load
Common Causes:
- Static initialization order issues (avoid global objects with non-trivial constructors)
- Missing or incompatible dependencies
- Exception thrown during plugin initialization (ensure proper exception handling)
See also