Overview
The IProtocol interface is the base class for all protocol plugins. Protocols are responsible for transmitting tracking data from OpenTrack to games and applications using various communication methods.
Interface Definition
struct IProtocol : module_status_mixin
{
IProtocol ();
~IProtocol () override ;
// Core methods (must implement)
virtual void pose ( const double* pose , const double* raw ) = 0 ;
virtual QString game_name () = 0 ;
// From module_status_mixin
virtual module_status initialize () = 0 ;
// Deleted methods (non-copyable)
IProtocol ( const IProtocol & ) = delete ;
IProtocol & operator = ( const IProtocol & ) = delete ;
};
Methods
initialize()
virtual module_status initialize () = 0 ;
Initializes the protocol and prepares for data transmission.
Returns status_ok() on success, or error(message) on failure.
Description:
Called once when the protocol is started
Open network connections, shared memory, or other resources
Validate configuration settings
Register with game interfaces
Return error if initialization fails
Example implementations:
proto-ft/ftnoir_protocol_ft.cpp
Example: UDP Protocol
module_status freetrack :: initialize ()
{
// Verify shared memory is available
if ( ! shm . success ())
return error ( tr ( "Can't load freetrack memory mapping" ));
// Set up protocol registry entries
if ( auto ret = set_protocols (); ! ret . is_ok ())
return ret;
// Initialize shared memory structure
pMemData -> data . DataID = 1 ;
pMemData -> data . CamWidth = 100 ;
pMemData -> data . CamHeight = 250 ;
store ( pMemData -> GameID2 , 0 );
for ( unsigned k = 0 ; k < 2 ; k ++ )
store ( pMemData -> table_ints [k], 0 );
// Start helper process if needed
if ( s . used_interface != settings ::enable_freetrack)
start_dummy ();
return status_ok ();
}
pose()
virtual void pose ( const double* pose , const double* raw ) = 0 ;
Transmits filtered and raw tracking data to the target application.
Filtered pose data as array: [TX, TY, TZ, Yaw, Pitch, Roll] in degrees/cm
Raw pose data (before filtering) in same format as pose
This method is called 250 times per second. Keep it fast and avoid blocking operations.
Description:
Called at 250Hz from the tracking pipeline thread
Transform data to target format
Send data via network, shared memory, or other IPC
Use background threads for expensive operations
Don’t block the calling thread
Data format:
pose[TX] / raw[TX]: X translation in centimeters
pose[TY] / raw[TY]: Y translation in centimeters
pose[TZ] / raw[TZ]: Z translation in centimeters
pose[Yaw] / raw[Yaw]: Yaw rotation in degrees
pose[Pitch] / raw[Pitch]: Pitch rotation in degrees
pose[Roll] / raw[Roll]: Roll rotation in degrees
Example implementation:
proto-ft/ftnoir_protocol_ft.cpp
Example: UDP Protocol
void freetrack :: pose ( const double* headpose , const double* raw )
{
constexpr double d2r = M_PI / 180 ; // Degrees to radians
// Convert to radians and scale translations
const float yaw = float ( - headpose [Yaw] * d2r);
const float roll = float ( headpose [Roll] * d2r);
const float tx = float ( headpose [TX] * 10 ); // cm to mm
const float ty = float ( headpose [TY] * 10 );
const float tz = float ( headpose [TZ] * 10 );
// Handle pitch discontinuity at 90 degrees
const bool is_crossing_90 = std :: fabs ( headpose [Pitch] - 90 ) < .15 ;
const float pitch = float ( - d2r *
(is_crossing_90 ? 89.86 : headpose [Pitch]));
// Write to shared memory atomically
FTHeap * const ft = pMemData;
FTData * const data = & ft -> data ;
store ( data -> X , tx);
store ( data -> Y , ty);
store ( data -> Z , tz);
store ( data -> Yaw , yaw);
store ( data -> Pitch , pitch);
store ( data -> Roll , roll);
// Also store raw data
store ( data -> RawYaw , float ( - raw [Yaw] * d2r));
store ( data -> RawPitch , float ( raw [Pitch] * d2r));
store ( data -> RawRoll , float ( raw [Roll] * d2r));
store ( data -> RawX , float ( raw [TX] * 10 ));
store ( data -> RawY , float ( raw [TY] * 10 ));
store ( data -> RawZ , float ( raw [TZ] * 10 ));
// Update game detection
const std :: int32_t id = load ( ft -> GameID );
if (intGameID != id)
{
// New game connected, update game name
QString gamename;
getGameData (id, gamename);
QMutexLocker foo ( & game_name_mutex);
connected_game = gamename . isEmpty () ?
tr ( "Unknown game" ) : gamename;
intGameID = id;
}
// Increment frame counter
InterlockedAdd ((LONG volatile * ) & data -> DataID , 1 );
}
game_name()
virtual QString game_name () = 0 ;
Returns the name of the currently connected game or application.
Name of connected game, or placeholder text if none detected
Description:
Called periodically from UI thread
Return actual game name if detected
Return generic text like “Game” if unknown
Use mutex protection if name is updated from pose()
Example implementations:
proto-ft/ftnoir_protocol_ft.cpp
Example: Static Name
Example: Dynamic Detection
QString freetrack :: game_name ()
{
QMutexLocker foo ( & game_name_mutex);
return connected_game;
}
Dialog Interface
struct IProtocolDialog : public BaseDialog
{
virtual void register_protocol ( IProtocol * protocol ) = 0 ;
virtual void unregister_protocol () = 0 ;
};
register_protocol()
virtual void register_protocol ( IProtocol * protocol ) = 0 ;
Receives a pointer to the active protocol instance.
Pointer to the running protocol instance
Description:
Called from UI thread when protocol starts
Store pointer for runtime interaction (optional)
Can query protocol state or display status
Often implemented as empty function
Example:
class MyProtocolDialog : public IProtocolDialog
{
IProtocol * protocol = nullptr ;
public:
void register_protocol ( IProtocol * p ) override
{
protocol = p;
// Could start status update timer here
}
void unregister_protocol () override
{
protocol = nullptr ;
}
};
unregister_protocol()
virtual void unregister_protocol () = 0 ;
Called when protocol is about to be destroyed.
Description:
Clear any stored protocol pointers
Stop accessing protocol data
Clean up any UI state
Complete Example
my-protocol.h
my-protocol.cpp
#pragma once
#include "api/plugin-api.hpp"
#include <QUdpSocket>
#include <QMutex>
class MyProtocol : public IProtocol
{
public:
MyProtocol ();
~MyProtocol () override ;
module_status initialize () override ;
void pose ( const double* pose , const double* raw ) override ;
QString game_name () override ;
private:
std ::unique_ptr < QUdpSocket > socket;
QHostAddress target_address;
quint16 target_port;
QMutex name_mutex;
QString current_game;
};
class MyProtocolDialog : public IProtocolDialog
{
Q_OBJECT
public:
MyProtocolDialog ();
void register_protocol ( IProtocol * p ) override {}
void unregister_protocol () override {}
};
class MyProtocolMetadata : public Metadata
{
Q_OBJECT
QString name () override { return tr ( "My Protocol" ); }
QIcon icon () override { return QIcon ( ":/images/icon.png" ); }
};
Communication Patterns
Used by FreeTrack/TrackIR protocols for low-latency local communication. class SharedMemoryProtocol : public IProtocol
{
shm_wrapper shm{ "SharedMemName" , "MutexName" , sizeof (Data)};
Data * pData = (Data * ) shm . ptr ();
module_status initialize () override
{
if ( ! shm . success ())
return error ( "Failed to create shared memory" );
return status_ok ();
}
void pose ( const double* pose , const double* raw ) override
{
// Atomic writes to shared memory
InterlockedExchange ( & pData -> x , pose [TX]);
InterlockedExchange ( & pData -> y , pose [TY]);
// ...
}
};
Simple datagram-based protocol for network transmission. void UDPProtocol :: pose ( const double* pose , const double* raw )
{
// Format as string
QString packet = QString ( "%1,%2,%3,%4,%5,%6" )
. arg ( pose [TX]). arg ( pose [TY]). arg ( pose [TZ])
. arg ( pose [Yaw]). arg ( pose [Pitch]). arg ( pose [Roll]);
// Send UDP datagram
socket -> writeDatagram ( packet . toUtf8 (),
target_address, target_port);
}
Emulate joystick or other input device. void VirtualDeviceProtocol :: pose ( const double* pose ,
const double* raw )
{
// Map to joystick axes
joystick_state . x = map_to_axis ( pose [TX], - 50 , 50 );
joystick_state . y = map_to_axis ( pose [TY], - 50 , 50 );
joystick_state . z = map_to_axis ( pose [TZ], - 50 , 50 );
joystick_state . rx = map_to_axis ( pose [Yaw], - 180 , 180 );
joystick_state . ry = map_to_axis ( pose [Pitch], - 90 , 90 );
joystick_state . rz = map_to_axis ( pose [Roll], - 180 , 180 );
// Update virtual device
device -> update_state (joystick_state);
}
The pose() method is called 250 times per second. Optimize carefully.
Best Practices
Use non-blocking I/O
// Good: non-blocking UDP send
socket -> writeDatagram (data, addr, port);
// Bad: blocking TCP send
socket -> write (data);
socket -> waitForBytesWritten (); // Blocks!
Minimize allocations
// Good: reuse buffer
class MyProtocol {
QByteArray buffer; // Reused across calls
void pose ( const double* pose , const double* raw ) override {
buffer . clear ();
// Fill buffer...
}
};
Batch updates if needed
void pose ( const double* pose , const double* raw ) override
{
buffer_pose (pose);
if ( ++ frame_count % 10 == 0 ) { // Send every 10th frame
flush_buffer ();
}
}
Use atomic operations for shared memory
// Good: atomic update
InterlockedExchange ( & pData -> value , new_value);
// Bad: non-atomic
pData -> value = new_value; // Race condition!
See Also