VR-Engage  2.2
Loading...
Searching...
No Matches
Player Attribute Store Example

Overview

Purpose: This example demonstrates how to monitor and respond to changes in the VR-Engage player attribute store using the attribute callback system. It illustrates event-driven component design where components react to state changes rather than polling for updates.

Observable Behavior: When running this example, you will see log messages in the VR-Engage console showing gear changes as you operate vehicle controls. The example monitors the "set-gear" attribute and logs both continuous polling updates (every frame) and event-driven change notifications (only when gear actually changes). This demonstrates the difference between polling and callback-based attribute monitoring approaches.

Prerequisites:

  • Understanding of Player Attribute Store architecture and usage patterns
  • Familiarity with C++ member function callbacks and std::function semantics
  • Knowledge of VR-Engage component lifecycle methods (initialize(), postInitialize(), tick(), shutdown())
  • Basic understanding of the observer pattern and event-driven programming concepts

Related Examples:

Key Concepts Demonstrated

This example demonstrates:

  1. Player Attribute Store Access - Centralized state repository for player-specific data
    • See Player Attribute Store for architectural details
    • Unique aspect in this example: Demonstrates both read access patterns (polling vs. callbacks) for the same attribute
  2. Attribute Callback System - Event-driven programming using the observer pattern
    • Implementation pattern: Connection manager (DtAttributeConnectionList) provides automatic callback lifetime management
    • Shows type-safe callback registration with automatic cleanup during component shutdown
  3. Component Lifecycle Management - Proper resource management across VR-Engage component states
    • Demonstrates critical timing differences between initialize() and postInitialize() for callback registration
    • Shows proper cleanup patterns in shutdown() to prevent dangling pointer issues
  4. Polling vs. Event-Driven Patterns - Comparison of continuous monitoring versus change notification
    • Illustrates performance trade-offs between tick() polling and attribute change callbacks
    • Demonstrates when each approach is appropriate for different types of state monitoring

Code Walkthrough

Component Registration

The plugin registers the component with VR-Engage's factory system:

// File: examples/playerStateAttribute/plugin.cxx
extern "C"
{
{
return true;
}
}
Example component demonstrating player attribute monitoring and callback patterns.
Definition sampleComponent.h:44
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.
constexpr auto DtPlayerStateAttributeSampleComponentType
Type identifier string for DtNotifyLogic component registration.
Definition sampleComponent.h:24

The string DtPlayerStateAttributeSampleComponentType must match the component's type() method return value. This registration makes the component available for instantiation when specified in role configurations. The extern "C" linkage prevents C++ name mangling, which is required for dynamic loading of plugin entry points.

Component Initialization and Attribute Access

The component demonstrates proper attribute store access during initialization:

// File: examples/playerStateAttribute/sampleComponent.cxx
bool DtPlayerStateAttributeSampleComponent::initialize(DtPlayerStation* player, DtInitTable& config)
{
if (!DtPlayerComponent::initialize(player, config))
{
return false;
}
return true;
}
virtual bool initialize(makVre::DtPlayerStation *player, makVre::DtInitTable &config) override
Initializes the component with player station and configuration.
makVre::DtAttributeHandle myGearAttributeHandle
Handle to the "set-gear" player attribute.
Definition sampleComponent.h:155
virtual DtAttributeHandle & playerAttributeStore()
Gets the player attribute store.
virtual DtPlayerStation & player()
Gets the player station.

The base class initialize() must be called first to set up inherited framework members before accessing the player attribute store. Attribute handles should be retrieved here when the player station is available. The "set-gear" attribute is a standard vehicle attribute managed by VR-Engage's control systems, and handles are lightweight value types that provide type-safe access to attribute values.

Callback Registration Timing

The component shows critical timing requirements for callback registration:

// File: examples/playerStateAttribute/sampleComponent.cxx
{
DtPlayerComponent::postInitialize();
return true;
}
virtual bool postInitialize() override
Performs post-initialization after all components are initialized.
virtual void onGearChanged(const std::string &newGear)
Callback invoked when the "set-gear" attribute changes.
void connect(DtAttributeHandle attr, OBJECT_T *object, void(OBJECT_T::*method)(const DtAttributeHandle &))
DtAttributeCallbackManager myAttributeCallbacks
Member-scope instance of Attribute callback manager.
Definition playerComponent.h:194

Callbacks must be registered in postInitialize() rather than initialize() because by postInitialize(), all components are fully initialized and attributes are guaranteed to exist. The connection manager automatically tracks callback lifetimes for proper cleanup. Registering callbacks too early in initialize() could cause race conditions or reference missing attributes that other components have not yet created.

Polling Pattern Implementation

The tick method demonstrates continuous attribute monitoring:

// File: examples/playerStateAttribute/sampleComponent.cxx
{
const auto currentGear = myGearAttributeHandle->get<std::string>();
LOG_VERBOSE("Player Attribute Sample") << "Current Gear: " << currentGear << std::endl;
}
virtual void tick(double dt) override
Per-frame update callback.
const T & get() const
Gets the value of the attribute.
#define LOG_VERBOSE(channel)
Macro to log a verbose message to log files.
Definition logger.h:79

Polling retrieves attribute values every frame regardless of whether they have changed. The get<T>() template method provides type-safe access to attribute values. This approach is suitable for continuous monitoring but can be inefficient for attributes that change infrequently, incurring approximately 60 attribute accesses per second at typical frame rates.

Event-Driven Callback Implementation

The callback method demonstrates efficient change notification:

// File: examples/playerStateAttribute/sampleComponent.cxx
{
LOG_INFO("Player Attribute Sample") << "Gear has changed! New Gear: " << newGear << std::endl;
}
#define LOG_INFO(channel)
Macro to log an informational message to log files.
Definition logger.h:74

Callbacks execute only when attribute values actually change, which is more efficient than polling. The new value is provided as a parameter, eliminating the need for additional attribute queries. Callback signatures must match the attribute's type (std::string for "set-gear"), and this pattern enables immediate responses to state changes without polling overhead.

Resource Cleanup

The shutdown method demonstrates proper callback lifecycle management:

// File: examples/playerStateAttribute/sampleComponent.cxx
{
DtPlayerComponent::shutdown();
}
virtual void shutdown() override
Shutdown callback for cleanup.

All attribute callbacks must be disconnected before component destruction because the attribute store may outlive individual components. Without explicit cleanup, the store could attempt to invoke callbacks on destroyed objects, causing crashes. The connection manager's disconnectAll() method provides convenient bulk cleanup for all registered callbacks.

Deployment and Testing

Installation

Build the example (see Environment Setup & Build Guide):

cd examples\build
cmake --build . --config RelWithDebInfo --target playerStateAttribute

Install the plugin to the VR-Engage installation:

cmake --install . --config RelWithDebInfo

This copies the plugin to the appropriate location:

  • Frontend plugin: <VR-Engage-Install-Dir>\plugins64\vrEngage\release\examplePlayerStateAttribute.dll

Verify installation:

dir "<VR-Engage-Install-Dir>\plugins64\vrEngage\release\examplePlayerStateAttribute.dll"

The simulation model set is located at <VR-Engage-Install-Dir>\data\simulationModelSets\examples\playerStateAttribute.sms and should be available in the standard VR-Engage installation.

Launching with the Example Simulation Model Set

To run VR-Engage with the playerStateAttribute example simulation model set as the default, use the --setting command-line option to override playerStationApp.defaultSimulationModelSets:

vrEngage.exe -c -n 3 --setting "playerStationApp.defaultSimulationModelSets = {'<VR-Engage-Install-Dir>/data/simulationModelSets/examples/playerStateAttribute.sms'};"

This is equivalent to the Visual Studio debugger configuration for the playerStateAttribute example and ensures that the correct simulation model set and roles are loaded when testing this example.

Configuration

Option 1: Use Visual Studio debug configuration (easiest):

  1. Right-click the playerStateAttribute project in Visual Studio → Debug → Start New Instance
  2. This automatically loads the playerStateAttribute.sms simulation model set with pre-configured entities

Option 2: Manual scenario setup:

  1. Create a scenario that includes the playerStateAttribute.sms simulation model set
  2. Add a ground vehicle entity (e.g., LAV III APC) to the scenario

Testing Procedure

  1. Launch VR-Engage with the playerStateAttribute example (using Option 1 above)
  2. Select a driver role and connect to the ground vehicle entity
  3. Expected Behavior:
    • Console shows continuous "Current Gear:" messages from polling in tick()
    • When you change gears using vehicle controls, you'll see "Gear has changed!" messages from the callback
    • Polling messages appear every frame with the current gear value
    • Change notification messages appear only when the gear value actually changes
  4. Test gear changes:
    • Use vehicle control inputs to shift gears (typically keyboard keys or joystick buttons)
    • Observe the difference between continuous polling output and event-driven callback output

Verification:

  • Check the VR-Engage log (the most recent *.log file in the MAK log directory, typically C:/MAK/logs) for component registration:
    [Player State Attribute] Initializing front-end
  • Verify both polling and callback messages appear during gear operation

Troubleshooting

Plugin not loading:

  • Verify DLL is in correct plugins directory: <VR-Engage-Install-Dir>\plugins64\vrEngage\release\
  • Check VR-Engage framework dependencies are available (vrePlayerStation.dll, vreUtil.dll)
  • Review the VR-Engage log (most recent *.log file in the MAK log directory) for DLL load errors or missing symbols

No attribute messages appearing:

  • Symptom: Component loads but no gear messages appear in the log
  • Cause: Not connected to a vehicle entity or "set-gear" attribute doesn't exist for the entity type
  • Solution: Ensure you're connected to a ground vehicle entity (tank, APC, truck) that has gear controls

Only polling messages, no callback messages:

  • Symptom: Continuous "Current Gear:" messages but no "Gear has changed!" messages
  • Cause: Callback registration failed or gear value isn't actually changing
  • Solution: Verify the entity has functional gear controls and try manual gear changes

Crash on component destruction:

  • Symptom: VR-Engage crashes when exiting or changing roles
  • Cause: Callbacks not properly disconnected in shutdown()
  • Solution: Verify myAttributeCallbacks.disconnectAll() is called in shutdown method

Technical Reference

File Structure

examples/playerStateAttribute/
├── CMakeLists.txt # Build configuration with debug setup
├── README.md # This documentation
├── plugin.h/.cxx # Plugin entry point and factory registration
├── sampleComponent.h/.cxx # Component implementation demonstrating attribute monitoring
└── export.h # DLL export macros

Key Classes

Class Base Class Purpose Header
DtPlayerStateAttributeSampleComponent DtPlayerComponent Attribute monitoring component sampleComponent.h

API Methods Used

  • DtPlayerStation::playerAttributeStore() - Access centralized attribute repository
  • DtAttributeHandle::get<T>() - Type-safe attribute value retrieval
  • DtAttributeConnectionList::connect() - Register attribute change callbacks
  • DtAttributeConnectionList::disconnectAll() - Bulk callback cleanup
  • DtPlayerStationApp::componentFactory().addCreator<T>() - Component factory registration

Attribute Details

Attribute Name Type Description Updated By
"set-gear" std::string Current gear selection (e.g., "Neutral", "1st", "2nd", "Reverse") Vehicle control components

Build Targets

  • Plugin: examplePlayerStateAttribute.dll (Windows)
  • Install Location: plugins64/vrEngage/release/
  • Debug Configuration: Automatically loads playerStateAttribute.sms simulation model set

Related Documentation: