tengri/Content/Input/Components/AC_InputDevice.ts

233 lines
7.7 KiB
TypeScript

// Input/Components/AC_InputDevice.ts
import type { AC_DebugHUD } from '#root/Debug/Components/AC_DebugHUD.ts';
import type { AC_ToastSystem } from '#root/Toasts/Components/AC_ToastSystem.ts';
import { ActorComponent } from '#root/UE/ActorComponent.ts';
import { EHardwareDevicePrimaryType } from '#root/UE/EHardwareDevicePrimaryType.ts';
import type { Float } from '#root/UE/Float.ts';
import { InputDeviceSubsystem } from '#root/UE/InputDeviceSubsystem.ts';
import type { Integer } from '#root/UE/Integer.ts';
import { SystemLibrary } from '#root/UE/SystemLibrary.ts';
import { E_MessageType } from '#root/UI/Enums/E_MessageType.ts';
/**
* Input Device Detection Component
* Minimal wrapper around Unreal Engine's native InputDeviceSubsystem
* Provides simple binary classification: Gamepad vs Everything Else
*/
export class AC_InputDevice extends ActorComponent {
// ════════════════════════════════════════════════════════════════════════════════════════
// FUNCTIONS
// ════════════════════════════════════════════════════════════════════════════════════════
/**
* Get current active input device type
* @returns Current device type (cached from delegate events)
* @category Device Queries
* @pure true
*/
public GetCurrentInputDevice(): EHardwareDevicePrimaryType {
return this.CurrentDevice;
}
/**
* Check if current device is keyboard/mouse
* @returns True if keyboard is active
* @category Device Queries
* @pure true
*/
public IsKeyboard(): boolean {
return this.CurrentDevice === EHardwareDevicePrimaryType.KeyboardAndMouse;
}
/**
* Check if current device is gamepad/controller
* @returns True if gamepad is active
* @category Device Queries
* @pure true
*/
public IsGamepad(): boolean {
return this.CurrentDevice === EHardwareDevicePrimaryType.Gamepad;
}
/**
* Initialize device detection system with delegate registration
* @param ToastComponentRef - Toast system for debug notifications
* @param DebugHUDComponentRef - Optional debug HUD for displaying device info
* @category System Setup
*/
public InitializeDeviceDetection(
ToastComponentRef: AC_ToastSystem,
DebugHUDComponentRef: AC_DebugHUD
): void {
this.ToastComponent = ToastComponentRef;
this.DebugHUDComponent = DebugHUDComponentRef;
this.RegisterHardwareDeviceDelegate();
this.DetectInitialDevice();
this.IsInitialized = true;
if (SystemLibrary.IsValid(this.DebugHUDComponent)) {
this.DebugHUDComponent.AddDebugPage(
this.DebugPageID,
'Input Device Info',
60
);
}
if (SystemLibrary.IsValid(this.ToastComponent)) {
this.ToastComponent.ShowToast(
`Device Detection Initialized: ${this.CurrentDevice}`,
E_MessageType.Success
);
}
}
/**
* Register OnInputHardwareDeviceChanged delegate
* Core of the event-driven approach
* @category System Setup
*/
private RegisterHardwareDeviceDelegate(): void {
InputDeviceSubsystem.OnInputHardwareDeviceChanged.BindEvent(
this.OnInputHardwareDeviceChanged.bind(this)
);
}
/**
* Detect initial device on system startup
* Fallback for getting device before first hardware change event
* @category System Setup
*/
private DetectInitialDevice(): void {
this.CurrentDevice =
InputDeviceSubsystem.GetMostRecentlyUsedHardwareDevice().PrimaryDeviceType;
}
/**
* Handle hardware device change events
* Called automatically when input hardware changes
* @param UserId - User ID (usually 0 for single-player)
* @param DeviceId - Device name string
* @category Event Handling
*/
private OnInputHardwareDeviceChanged(
UserId?: Integer,
DeviceId?: Integer
): void {
this.ProcessDeviceChange(
InputDeviceSubsystem.GetInputDeviceHardwareIdentifier(DeviceId ?? 0)
.PrimaryDeviceType
);
}
/**
* Process device change with debouncing
* Prevents rapid switching and manages device state
* @param NewDevice - Newly detected device type
* @category Device Management
*/
private ProcessDeviceChange(NewDevice: EHardwareDevicePrimaryType): void {
if (NewDevice !== this.CurrentDevice && this.CanProcessDeviceChange()) {
this.CurrentDevice = NewDevice;
this.LastDeviceChangeTime = SystemLibrary.GetGameTimeInSeconds();
if (SystemLibrary.IsValid(this.ToastComponent)) {
this.ToastComponent.ShowToast(
`Input switched to ${NewDevice}`,
E_MessageType.Info
);
}
}
}
/**
* Check if device change can be processed (debouncing)
* Prevents rapid device switching within cooldown period
* @returns True if enough time has passed since last change
* @category Debouncing
* @pure true
*/
private CanProcessDeviceChange(): boolean {
const HasCooldownExpired = (): boolean =>
SystemLibrary.GetGameTimeInSeconds() - this.LastDeviceChangeTime >=
this.DeviceChangeCooldown;
return HasCooldownExpired();
}
/**
* Update debug HUD with current device info
* @category Debug
*/
public UpdateDebugPage(): void {
if (SystemLibrary.IsValid(this.DebugHUDComponent)) {
if (
this.DebugHUDComponent.ShouldUpdatePage(
this.DebugPageID,
SystemLibrary.GetGameTimeInSeconds()
)
) {
this.DebugHUDComponent.UpdatePageContent(
this.DebugPageID,
`Hardware Device Identifier: ${this.GetCurrentInputDevice()}` +
`Initialized: ${this.IsInitialized}` +
`Last Change: ${this.LastDeviceChangeTime}`
);
}
}
}
// ════════════════════════════════════════════════════════════════════════════════════════
// VARIABLES
// ════════════════════════════════════════════════════════════════════════════════════════
/**
* Reference to toast system for debug notifications
* @category Components
*/
private ToastComponent: AC_ToastSystem | null = null;
/**
* Reference to debug HUD component for displaying camera info
* Optional, used for debugging purposes
* @category Components
*/
public DebugHUDComponent: AC_DebugHUD | null = null;
/**
* Current active input device type
* Updated by hardware device change events
* @category Device State
*/
private CurrentDevice: EHardwareDevicePrimaryType =
EHardwareDevicePrimaryType.Unspecified;
/**
* System initialization status
* @category System State
*/
public IsInitialized: boolean = false;
/**
* Last device change timestamp for debouncing
* @category Debouncing
*/
public LastDeviceChangeTime: Float = 0;
/**
* Minimum time between device changes (prevents flickering)
* Recommended: 300-500ms for most games
* @category Debouncing
* @instanceEditable true
*/
private DeviceChangeCooldown: Float = 0.3;
/**
* Debug page identifier for organizing debug output
* Used by debug HUD to categorize information
* @category Debug
* @instanceEditable true
*/
public readonly DebugPageID: string = 'InputDeviceInfo';
}