Overview
CommunicationBackend is an abstract base class that orchestrates the flow of data from input sources through game modes to output reports. It manages input scanning, output generation, and provides the interface for sending reports to the host device via USB or other communication protocols.
Class Definition
class CommunicationBackend {
public:
CommunicationBackend(
InputState &inputs,
InputSource **input_sources,
size_t input_source_count
);
virtual ~CommunicationBackend(){}
InputState &GetInputs();
OutputState &GetOutputs();
void ScanInputs();
void ScanInputs(InputScanSpeed input_source_filter);
virtual void UpdateOutputs();
virtual CommunicationBackendId BackendId();
virtual void SetGameMode(InputMode *gamemode);
virtual InputMode *CurrentGameMode();
virtual void SendReport() = 0;
protected:
InputState &_inputs;
InputSource **_input_sources;
size_t _input_source_count;
OutputState _outputs;
InputMode *_gamemode = nullptr;
private:
void ResetOutputs();
};
Constructor & Destructor
CommunicationBackend
CommunicationBackend::CommunicationBackend(
InputState &inputs,
InputSource **input_sources,
size_t input_source_count
)
Initializes a communication backend instance.
Reference to the shared input state structure. This is typically a global or static variable that persists for the lifetime of the program.
Array of pointers to input source objects. The backend will scan these sources to populate the input state.
Number of input sources in the array.
Implementation:
- Stores references to inputs and input sources
- Initializes game mode to
nullptr
- Does not take ownership of input sources (caller must manage their lifetime)
~CommunicationBackend
virtual ~CommunicationBackend(){}
Virtual destructor for proper cleanup of derived classes.
Public Methods
Returns a reference to the current input state.
Returns: Reference to the InputState structure containing all button states.
Usage:
InputState &inputs = backend.GetInputs();
if (inputs.lf1) {
// Handle button press
}
GetOutputs
OutputState &GetOutputs()
Returns a reference to the current output state.
Returns: Reference to the OutputState structure containing all controller outputs.
Usage:
OutputState &outputs = backend.GetOutputs();
Serial.print("Left Stick X: ");
Serial.println(outputs.leftStickX);
Scans all input sources and updates the input state.
Behavior:
- Calls
UpdateInputs() on every registered input source
- Combines inputs from all sources into the shared input state
Usage:
void loop() {
backend.ScanInputs(); // Read all buttons
backend.UpdateOutputs(); // Process through game mode
backend.SendReport(); // Send to host
}
void ScanInputs(InputScanSpeed input_source_filter)
Selectively scans only input sources that match the specified scan speed.
Only scan input sources with this scan speed (SLOW, MEDIUM, or FAST)
Behavior:
- Iterates through all input sources
- Calls
UpdateInputs() only on sources where ScanSpeed() matches the filter
- Useful for optimizing performance by scanning high-priority inputs more frequently
Usage:
void loop() {
// Scan high-priority button inputs every iteration
backend.ScanInputs(InputScanSpeed::FAST);
backend.UpdateOutputs();
backend.SendReport();
// Scan lower-priority inputs less frequently
static uint32_t last_scan = 0;
if (millis() - last_scan > 100) {
backend.ScanInputs(InputScanSpeed::MEDIUM);
backend.ScanInputs(InputScanSpeed::SLOW);
last_scan = millis();
}
}
UpdateOutputs
virtual void UpdateOutputs()
Processes the current input state through the active game mode to generate outputs.
Behavior:
- Resets output state to neutral via
ResetOutputs()
- If a game mode is set, calls
_gamemode->UpdateOutputs(_inputs, _outputs)
- If no game mode is set, outputs remain at neutral
Usage:
backend.ScanInputs();
backend.UpdateOutputs(); // Convert inputs to outputs
backend.SendReport(); // Send outputs to host
This method is virtual and can be overridden by derived backends that need custom output processing logic.
BackendId
virtual CommunicationBackendId BackendId()
Returns the identifier for this backend type.
Returns: A CommunicationBackendId enum value identifying the backend.
Default Implementation: Returns COMMS_BACKEND_UNSPECIFIED
Common Backend IDs:
COMMS_BACKEND_DINPUT - DirectInput (generic USB gamepad)
COMMS_BACKEND_XINPUT - XInput (Xbox 360 controller)
COMMS_BACKEND_NINTENDO_SWITCH - Nintendo Switch Pro Controller
COMMS_BACKEND_GAMECUBE - GameCube controller
COMMS_BACKEND_N64 - N64 controller
COMMS_BACKEND_SNES - SNES controller
COMMS_BACKEND_NES - NES controller
SetGameMode
virtual void SetGameMode(InputMode *gamemode)
Sets the active game mode for input processing.
Pointer to the game mode to activate. Can be nullptr to disable output processing.
Usage:
Melee20Button melee_mode;
backend.SetGameMode(&melee_mode);
// Later, switch modes
Ultimate ultimate_mode;
backend.SetGameMode(&ultimate_mode);
The backend does not take ownership of the game mode object. Ensure the game mode remains valid for the lifetime it’s set as active.
CurrentGameMode
virtual InputMode *CurrentGameMode()
Returns a pointer to the currently active game mode.
Returns: Pointer to the current InputMode, or nullptr if no mode is set.
Usage:
InputMode *mode = backend.CurrentGameMode();
if (mode != nullptr) {
GameModeConfig *config = mode->GetConfig();
// Access mode configuration
}
// Check for specific mode type
if (KeyboardMode *kb = dynamic_cast<KeyboardMode*>(backend.CurrentGameMode())) {
kb->SendReport(backend.GetInputs());
}
Pure Virtual Methods
SendReport
virtual void SendReport() = 0
Sends the current output state to the host device.
Implementation Requirements:
- Read the current
_outputs state
- Format and send the appropriate USB HID report for the backend type
- Handle any backend-specific communication protocols
Example Implementation:
See Built-in Backend Implementations below.
Protected Members
Reference to the shared input state structure.
InputSource **_input_sources;
Array of pointers to registered input sources.
size_t _input_source_count;
Number of input sources in the array.
_outputs
The current output state generated by the game mode.
_gamemode
InputMode *_gamemode = nullptr;
Pointer to the currently active game mode.
Built-in Backend Implementations
HayBox includes several concrete implementations of CommunicationBackend for different output protocols:
class DInputBackend : public CommunicationBackend {
public:
DInputBackend(InputState &inputs, InputSource **input_sources, size_t input_source_count);
~DInputBackend();
CommunicationBackendId BackendId();
void SendReport();
private:
TUGamepad _gamepad;
};
Provides DirectInput (generic USB gamepad) support with 18 buttons and 6 analog axes.
Emulates an Xbox 360 controller. Compatible with games that require XInput.
NintendoSwitchBackend
Emulates a Nintendo Switch Pro Controller with proper USB descriptors and protocol.
GamecubeBackend
Communicates using the GameCube controller protocol over a data pin.
N64Backend
Communicates using the N64 controller protocol.
SnesBackend / NesBackend
Communicates using SNES/NES controller protocols.
Usage Example
Basic Setup
#include "core/CommunicationBackend.hpp"
#include "comms/DInputBackend.hpp"
#include "input/GpioButtonInput.hpp"
#include "modes/Melee20Button.hpp"
// Global input state
InputState input_state;
// Define input sources
const GpioButtonMapping button_mappings[] = { /* ... */ };
GpioButtonInput gpio_input(button_mappings, button_count);
InputSource *input_sources[] = {
&gpio_input,
};
// Create backend
DInputBackend backend(
input_state,
input_sources,
sizeof(input_sources) / sizeof(InputSource*)
);
// Create and set game mode
Melee20Button melee_mode;
void setup() {
backend.SetGameMode(&melee_mode);
}
void loop() {
backend.ScanInputs();
backend.UpdateOutputs();
backend.SendReport();
}
Handling Keyboard Mode
Keyboard modes require special handling:
void loop() {
backend.ScanInputs();
// Check if current mode is a keyboard mode
if (KeyboardMode *kb_mode = dynamic_cast<KeyboardMode*>(backend.CurrentGameMode())) {
// Keyboard mode: send keyboard HID report
kb_mode->SendReport(backend.GetInputs());
} else {
// Controller mode: send controller HID report
backend.UpdateOutputs();
backend.SendReport();
}
}
Mode Switching
Melee20Button melee_mode;
Ultimate ultimate_mode;
DefaultKeyboardMode keyboard_mode;
void loop() {
backend.ScanInputs();
// Check for mode switch buttons
if (backend.GetInputs().mb1 && backend.GetInputs().mb2) {
backend.SetGameMode(&melee_mode);
} else if (backend.GetInputs().mb2 && backend.GetInputs().mb3) {
backend.SetGameMode(&ultimate_mode);
} else if (backend.GetInputs().mb1 && backend.GetInputs().mb3) {
backend.SetGameMode(&keyboard_mode);
}
// Process and send
if (KeyboardMode *kb = dynamic_cast<KeyboardMode*>(backend.CurrentGameMode())) {
kb->SendReport(backend.GetInputs());
} else {
backend.UpdateOutputs();
backend.SendReport();
}
}
void loop() {
// Scan critical button inputs every iteration (1ms intervals)
backend.ScanInputs(InputScanSpeed::FAST);
// Process and send outputs
backend.UpdateOutputs();
backend.SendReport();
// Scan less critical inputs every 100ms
static uint32_t last_slow_scan = 0;
if (millis() - last_slow_scan >= 100) {
backend.ScanInputs(InputScanSpeed::MEDIUM);
backend.ScanInputs(InputScanSpeed::SLOW);
last_slow_scan = millis();
}
}
Implementing a Custom Backend
class CustomBackend : public CommunicationBackend {
public:
CustomBackend(
InputState &inputs,
InputSource **input_sources,
size_t input_source_count
) : CommunicationBackend(inputs, input_sources, input_source_count) {
// Initialize custom hardware/protocol
}
CommunicationBackendId BackendId() override {
return COMMS_BACKEND_CUSTOM;
}
void SendReport() override {
// Format output state into custom protocol
uint8_t report[64];
// Map buttons
report[0] = (_outputs.a ? 0x01 : 0) |
(_outputs.b ? 0x02 : 0) |
(_outputs.x ? 0x04 : 0) |
(_outputs.y ? 0x08 : 0);
// Map analog sticks
report[1] = _outputs.leftStickX;
report[2] = _outputs.leftStickY;
report[3] = _outputs.rightStickX;
report[4] = _outputs.rightStickY;
// Send via custom protocol
sendCustomReport(report, sizeof(report));
}
private:
void sendCustomReport(uint8_t *data, size_t length) {
// Hardware-specific transmission code
}
};
Best Practices
Never call UpdateOutputs() more frequently than you call ScanInputs(). This can result in stale output reports being sent to the host.
For optimal latency, keep the main loop execution time under 1ms. Profile your code to ensure ScanInputs(), UpdateOutputs(), and SendReport() complete quickly.
Always check if a game mode is set before attempting to access it. The backend initializes with _gamemode = nullptr.
Data Flow
The typical data flow through a communication backend:
┌─────────────────┐
│ Input Sources │ Physical buttons, switches, controllers
└────────┬────────┘
│
↓ ScanInputs()
┌─────────────────┐
│ InputState │ Unified button state (lf1, rf2, etc.)
└────────┬────────┘
│
↓ UpdateOutputs()
┌─────────────────┐
│ Game Mode │ Melee20Button, Ultimate, etc.
│ (InputMode) │ - Button remapping
└────────┬────────┘ - SOCD resolution
│ - Mode-specific logic
↓
┌─────────────────┐
│ OutputState │ Controller outputs (A, B, sticks, etc.)
└────────┬────────┘
│
↓ SendReport()
┌─────────────────┐
│ USB HID Report │ Sent to host device
└─────────────────┘
See Also