tengri/Content/Debug/Components/AC_DebugHUD.ts

681 lines
23 KiB
TypeScript

// Debug/Components/AC_DebugHUD.ts
import type { AC_Camera } from '#root/Camera/Components/AC_Camera.ts';
import { E_DebugUpdateFunction } from '#root/Debug/Enums/E_DebugUpdateFunction.ts';
import type { S_DebugPage } from '#root/Debug/Structs/S_DebugPage.ts';
import type { S_DebugSettings } from '#root/Debug/Structs/S_DebugSettings.ts';
import { DT_DebugPages } from '#root/Debug/Tables/DT_DebugPages.ts';
import { WBP_DebugHUD } from '#root/Debug/UI/WBP_DebugHUD.ts';
import type { AC_InputDevice } from '#root/Input/Components/AC_InputDevice.ts';
import type { AC_Movement } from '#root/Movement/Components/AC_Movement.ts';
import type { AC_ToastSystem } from '#root/Toasts/Components/AC_ToastSystem.ts';
import { ActorComponent } from '#root/UE/ActorComponent.ts';
import { CreateWidget } from '#root/UE/CteateWidget.ts';
import type { DataTable } from '#root/UE/DataTable.ts';
import { DataTableFunctionLibrary } from '#root/UE/DataTableFunctionLibrary.ts';
import { ESlateVisibility } from '#root/UE/ESlateVisibility.ts';
import type { Float } from '#root/UE/Float.ts';
import type { Integer } from '#root/UE/Integer.ts';
import { StringLibrary } from '#root/UE/StringLibrary.ts';
import { SystemLibrary } from '#root/UE/SystemLibrary.ts';
import type { Text } from '#root/UE/Text.ts';
import { UEArray } from '#root/UE/UEArray.ts';
import { E_MessageType } from '#root/UI/Enums/E_MessageType.ts';
/**
* Debug HUD Controller Component
* Manages debug information display system for deterministic movement
* Provides real-time performance monitoring and parameter visualization
*/
export class AC_DebugHUD extends ActorComponent {
// ════════════════════════════════════════════════════════════════════════════════════════
// FUNCTIONS
// ════════════════════════════════════════════════════════════════════════════════════════
/**
* Check if debug HUD should be visible
* @returns True if system is initialized and mode is visible
* @category HUD Control
* @pure true
*/
private ShouldShowDebugHUD(): boolean {
const IsSystemReadyAndVisible = (modeIsVisible: boolean): boolean =>
this.IsInitialized && modeIsVisible;
return IsSystemReadyAndVisible(
this.DebugSettings.CurrentMode === ESlateVisibility.Visible
);
}
/**
* Check if debug HUD should update this frame
* @example
* // Update every frame (UpdateFrequency = 0)
* ShouldUpdateDebugHUD(gameTime) // returns true
* // Update at 30Hz (UpdateFrequency = 30)
* ShouldUpdateDebugHUD(gameTime) // returns true every 1/30 seconds
* @param CurrentTime - Current game time in seconds
* @returns True if enough time has passed since last update
* @pure true
* @category HUD Control
*/
private ShouldUpdateDebugHUD(CurrentTime: Float = 0): boolean {
const IsFrequencyLimited = (updateFrequency: Float): boolean =>
updateFrequency > 0;
const IsUpdateIntervalReached = (
currentTime: Float,
updateFrequency: Float
): boolean => currentTime - this.LastUpdateTime >= 1 / updateFrequency;
if (IsFrequencyLimited(this.DebugSettings.UpdateFrequency)) {
return IsUpdateIntervalReached(
CurrentTime,
this.DebugSettings.UpdateFrequency
);
} else {
return true; // Update every frame
}
}
/**
* Register or update a debug page in the system
* @example
* // Register movement constants page
* RegisterDebugPage({
* PageID: E_DebugPageID.MovementInfo,
* Title: "Movement Constants",
* Content: "",
* IsVisible: true,
* UpdateFunction: E_DebugUpdateFunction.UpdateMovementPage
* })
* @param PageData - Page configuration and content data
* @category Page Management
*/
private RegisterDebugPage(PageData: S_DebugPage): void {
let existingIndex: Integer = -1;
this.DebugPages.forEach((page, index) => {
if (page.PageID === PageData.PageID) {
existingIndex = index;
return;
}
});
const IsPageExists = (): boolean => existingIndex >= 0;
if (IsPageExists()) {
this.DebugPages.SetArrayElem(existingIndex, PageData);
} else {
this.DebugPages.Add(PageData);
}
}
/**
* Get all currently visible debug pages
* @returns Array of visible pages only
* @category Page Management
* @pure true
*/
public GetVisiblePages(): UEArray<S_DebugPage> {
const filteredPages: UEArray<S_DebugPage> = new UEArray([]);
this.DebugPages.forEach(page => {
if (page.IsVisible) {
filteredPages.Add(page);
}
});
return filteredPages;
}
/**
* Get currently selected debug page
* @returns Object with page data and validity flag
* @category Page Management
* @pure true
*/
private GetCurrentPage():
| { Page: S_DebugPage | null; IsFound: true }
| { Page: null; IsFound: false } {
const IsPageIndexInvalid = (
length: Integer,
currentPageIndex: Integer
): boolean => length === 0 || currentPageIndex >= length;
return IsPageIndexInvalid(
this.GetVisiblePages().length,
this.DebugSettings.CurrentPageIndex
)
? { Page: null, IsFound: false }
: {
Page: this.GetVisiblePages().Get(this.DebugSettings.CurrentPageIndex),
IsFound: true,
};
}
/**
* Toggle debug HUD visibility between visible and hidden
* @category Navigation
*/
public ToggleDebugHUD(): void {
this.DebugSettings.CurrentMode =
this.DebugSettings.CurrentMode === ESlateVisibility.Visible
? ESlateVisibility.Hidden
: ESlateVisibility.Visible;
this.UpdateWidgetVisibility();
}
/**
* Navigate to previous debug page
* Wraps around to last page if at beginning
* @category Navigation
*/
public PreviousPage(): void {
const length = this.GetVisiblePages().length;
if (length > 1) {
const currentPage = this.DebugSettings.CurrentPageIndex;
const isAtFirstPage = (): boolean => currentPage - 1 < 0;
const getLastPageIndex = (): Integer => length - 1;
const getPreviousPageIndex = (): Integer => currentPage - 1;
this.DebugSettings.CurrentPageIndex = isAtFirstPage()
? getLastPageIndex()
: getPreviousPageIndex();
this.UpdateCurrentPage();
}
}
/**
* Navigate to next debug page
* Wraps around to first page if at end
* @category Navigation
*/
public NextPage(): void {
const length = this.GetVisiblePages().length;
const HasMultiplePages = (): boolean => length > 1;
const GetNextPageIndex = (currentPageIndex: Integer): Integer =>
(currentPageIndex + 1) % length;
if (HasMultiplePages()) {
this.DebugSettings.CurrentPageIndex = GetNextPageIndex(
this.DebugSettings.CurrentPageIndex
);
this.UpdateCurrentPage();
}
}
/**
* Toggle visual debug rendering (collision shapes, rays, etc.)
* @category Visual Debug
*/
public ToggleVisualDebug(): void {
this.DebugSettings.ShowVisualDebug = !this.DebugSettings.ShowVisualDebug;
if (SystemLibrary.IsValid(this.ToastComponent)) {
this.ToastComponent.ShowToast(
`Visual Debug ${this.DebugSettings.ShowVisualDebug ? 'Enabled' : 'Disabled'}`
);
}
}
/**
* Update content of currently selected page
* Calls appropriate update function based on page type
* @category Page Updates
*/
private UpdateCurrentPage(): void {
let CurrentPage = this.GetCurrentPage().Page;
if (this.GetCurrentPage().IsFound && CurrentPage !== null) {
switch (CurrentPage.UpdateFunction) {
case E_DebugUpdateFunction.UpdateMovementPage: {
CurrentPage = this.UpdateMovementPage(CurrentPage);
break;
}
case E_DebugUpdateFunction.UpdateSurfacePage: {
CurrentPage = this.UpdateSurfacePage(CurrentPage);
break;
}
case E_DebugUpdateFunction.UpdatePerformancePage: {
CurrentPage = this.UpdatePerformancePage(CurrentPage);
break;
}
case E_DebugUpdateFunction.UpdateInputDevicePage: {
CurrentPage = this.UpdateInputDevicePage(CurrentPage);
break;
}
case E_DebugUpdateFunction.UpdateCameraPage: {
CurrentPage = this.UpdateCameraPage(CurrentPage);
break;
}
}
this.DebugPages.SetArrayElem(
this.DebugSettings.CurrentPageIndex,
CurrentPage
);
this.UpdateWidgetPage();
}
}
/**
* Update movement constants page content
* @param Page - Page structure to update
* @returns Updated page with current movement data
* @category Page Updates
*/
public UpdateMovementPage(Page: S_DebugPage): S_DebugPage {
if (SystemLibrary.IsValid(this.MovementComponent)) {
return {
PageID: Page.PageID,
Title: Page.Title,
Content:
// Constants
`Max Speed: ${this.MovementComponent.MovementConstants.MaxSpeed}\n` +
`Acceleration: ${this.MovementComponent.MovementConstants.Acceleration}\n` +
`Friction: ${this.MovementComponent.MovementConstants.Friction}\n` +
`Gravity: ${this.MovementComponent.MovementConstants.Gravity}\n` +
`\n` + // Разделитель
// Current State
`Current Velocity: ${StringLibrary.ConvVectorToString(this.MovementComponent.CurrentVelocity)}\n` +
`Speed: ${this.MovementComponent.CurrentSpeed}\n` +
`Is Grounded: ${this.MovementComponent.IsGrounded}\n` +
`Surface Type: ${this.MovementComponent.CurrentSurface}\n` +
`Movement State: ${this.MovementComponent.MovementState}\n` +
`Input Magnitude: ${this.MovementComponent.InputMagnitude}`,
IsVisible: Page.IsVisible,
UpdateFunction: Page.UpdateFunction,
};
} else {
return {
PageID: Page.PageID,
Title: Page.Title,
Content: 'Movement Component Not Found',
IsVisible: Page.IsVisible,
UpdateFunction: Page.UpdateFunction,
};
}
}
/**
* Update surface classification page content
* @param Page - Page structure to update
* @returns Updated page with current surface angle thresholds
* @category Page Updates
*/
public UpdateSurfacePage(Page: S_DebugPage): S_DebugPage {
if (SystemLibrary.IsValid(this.MovementComponent)) {
return {
PageID: Page.PageID,
Title: Page.Title,
Content:
`Walkable: ≤${this.MovementComponent.AngleThresholdsDegrees.Walkable}°\n` +
`Steep Slope: ${this.MovementComponent.AngleThresholdsDegrees.Walkable}°-${this.MovementComponent.AngleThresholdsDegrees.SteepSlope}°\n` +
`Wall: ${this.MovementComponent.AngleThresholdsDegrees.SteepSlope}°-${this.MovementComponent.AngleThresholdsDegrees.Wall}°\n` +
`Ceiling: >${this.MovementComponent.AngleThresholdsDegrees.Wall}°`,
IsVisible: Page.IsVisible,
UpdateFunction: Page.UpdateFunction,
};
} else {
return {
PageID: Page.PageID,
Title: Page.Title,
Content: 'Movement Component Not Found',
IsVisible: Page.IsVisible,
UpdateFunction: Page.UpdateFunction,
};
}
}
/**
* Update performance metrics page content
* @param Page - Page structure to update
* @returns Updated page with current performance data
* @category Page Updates
*/
public UpdatePerformancePage(Page: S_DebugPage): S_DebugPage {
if (SystemLibrary.IsValid(this.MovementComponent)) {
const IsUpdatingEveryFrame = (updateFrequency: Float): boolean =>
updateFrequency <= 0;
return {
PageID: Page.PageID,
Title: Page.Title,
Content:
`Frame: ${this.FrameCounter}\n` +
`FPS: ${this.FPS}\n` +
`Update Rate: ${IsUpdatingEveryFrame(this.DebugSettings.UpdateFrequency) ? 'Every Frame' : `${this.DebugSettings.UpdateFrequency} Hz`}\n` +
`ActivePages: ${this.GetVisiblePages().length}`,
IsVisible: Page.IsVisible,
UpdateFunction: Page.UpdateFunction,
};
} else {
return {
PageID: Page.PageID,
Title: Page.Title,
Content: 'Movement Component Not Found',
IsVisible: Page.IsVisible,
UpdateFunction: Page.UpdateFunction,
};
}
}
/**
* Update input device information page content
* @param Page - Page structure to update
* @returns Updated page with current input device data
* @category Page Updates
*/
public UpdateInputDevicePage(Page: S_DebugPage): S_DebugPage {
if (SystemLibrary.IsValid(this.InputDeviceComponent)) {
return {
PageID: Page.PageID,
Title: Page.Title,
Content:
`Hardware Device Identifier: ${this.InputDeviceComponent.GetCurrentInputDevice()}` +
`Initialized: ${this.InputDeviceComponent.IsInitialized}` +
`Last Change: ${this.InputDeviceComponent.LastDeviceChangeTime}`,
IsVisible: Page.IsVisible,
UpdateFunction: Page.UpdateFunction,
};
} else {
return {
PageID: Page.PageID,
Title: Page.Title,
Content: 'Input Device Component Not Found',
IsVisible: Page.IsVisible,
UpdateFunction: Page.UpdateFunction,
};
}
}
/**
* Update camera system information page content
* @param Page - Page structure to update
* @returns Updated page with current camera data
* @category Page Updates
*/
public UpdateCameraPage(Page: S_DebugPage): S_DebugPage {
if (SystemLibrary.IsValid(this.CameraComponent)) {
return {
PageID: Page.PageID,
Title: Page.Title,
Content:
`Current Device: ${SystemLibrary.IsValid(this.InputDeviceComponent) ? this.InputDeviceComponent.GetCurrentInputDevice() : 'Input Device Component Not Found'}\n` +
`Sensitivity: ${SystemLibrary.IsValid(this.InputDeviceComponent) && this.InputDeviceComponent.IsGamepad() ? this.CameraComponent.CameraSettings.GamepadSensitivity : this.CameraComponent.CameraSettings.MouseSensitivity}\n` +
`Pitch: ${this.CameraComponent.GetCameraRotation().Pitch}°\n` +
`Yaw: ${this.CameraComponent.GetCameraRotation().Yaw}°\n` +
`Is Rotating: ${this.CameraComponent.IsCameraRotating() ? 'Yes' : 'No'}\n` +
`Smoothing: ${this.CameraComponent.CameraSettings.SmoothingSpeed}\n` +
`Invert Y: ${this.CameraComponent.CameraSettings.InvertYAxis ? 'Yes' : 'No'}`,
IsVisible: Page.IsVisible,
UpdateFunction: Page.UpdateFunction,
};
} else {
return {
PageID: Page.PageID,
Title: Page.Title,
Content: 'Camera Component Not Found',
IsVisible: Page.IsVisible,
UpdateFunction: Page.UpdateFunction,
};
}
}
/**
* Create debug widget instance and add to viewport
* @category Widget Control
*/
private CreateDebugWidget(): void {
this.DebugWidget = CreateWidget(WBP_DebugHUD);
this.DebugWidget.MovementComponent = this.MovementComponent;
if (SystemLibrary.IsValid(this.DebugWidget)) {
this.DebugWidget.AddToViewport();
} else {
if (SystemLibrary.IsValid(this.ToastComponent)) {
this.ToastComponent.ShowToast('Failed to create debug widget');
}
}
}
/**
* Update widget visibility based on current debug mode
* @category Widget Control
*/
private UpdateWidgetVisibility(): void {
if (SystemLibrary.IsValid(this.DebugWidget)) {
this.DebugWidget.SetVisibility(
this.ShouldShowDebugHUD()
? ESlateVisibility.Visible
: ESlateVisibility.Hidden
);
}
}
/**
* Send current page data to debug widget for display
* @category Widget Communication
*/
private UpdateWidgetPage(): void {
if (
this.GetCurrentPage().IsFound &&
SystemLibrary.IsValid(this.DebugWidget)
) {
const currentPage = this.GetCurrentPage().Page;
const visiblePages = this.GetVisiblePages();
this.DebugWidget.SetHeaderText(currentPage!.Title);
this.DebugWidget.SetContentText(currentPage!.Content);
this.DebugWidget.SetNavigationText(
`Page ${this.DebugSettings.CurrentPageIndex + 1}/${visiblePages.length} | ${this.GetControlHints()} - Navigate`
);
}
}
private GetControlHints(): Text {
if (SystemLibrary.IsValid(this.InputDeviceComponent)) {
if (this.InputDeviceComponent.IsGamepad()) {
return 'D-Pad Up/Down';
}
}
return 'PageUp/PageDown';
}
/**
* Main update loop for debug HUD system
* Called every frame from game loop
* @param CurrentTime - Current game time in seconds
* @param DeltaTime - Time since last frame in seconds
* @category HUD Rendering
*/
public UpdateHUD(CurrentTime: Float, DeltaTime: Float): void {
const IsReadyForUpdate = (shouldUpdateHUD: boolean): boolean =>
this.IsInitialized && shouldUpdateHUD;
const IsValidDeltaTime = (deltaTime: Float): boolean => deltaTime > 0;
const CalculateFPS = (deltaTime: Float): Float => 1 / deltaTime;
if (IsReadyForUpdate(this.ShouldUpdateDebugHUD(CurrentTime))) {
this.FrameCounter++;
this.FPS = IsValidDeltaTime(DeltaTime) ? CalculateFPS(DeltaTime) : 0;
this.LastUpdateTime = CurrentTime;
if (this.ShouldShowDebugHUD()) {
this.UpdateCurrentPage();
this.UpdateWidgetPage();
}
}
}
/**
* Register default debug pages (Movement, Surface, Performance)
* @category System Setup
*/
private RegisterDefaultPages(): void {
DataTableFunctionLibrary.GetDataTableRowNames(this.DebugDataTable).forEach(
arrayElement => {
this.DebugDataTable.GetDataTableRow(arrayElement, row => {
this.RegisterDebugPage(row);
});
}
);
}
/**
* Get comprehensive test data for debug system validation
* @returns Object containing initialization status, DataTable reference, and pages array
* @category Testing
*/
public GetTestData(): {
IsInitialized: boolean;
DebugDataTable: DataTable<S_DebugPage>;
DebugPages: UEArray<S_DebugPage>;
} {
return {
IsInitialized: this.IsInitialized,
DebugDataTable: this.DebugDataTable,
DebugPages: this.DebugPages,
};
}
/**
* Initialize debug HUD system with movement component reference
* Sets up pages, creates widget, runs tests, and starts display
* @param MovementComponentRef - Reference to movement component to debug
* @param ToastComponentRef - Reference to toast system for notifications
* @param InputDeviceComponentRef - Reference to input device component for device info
* @param CameraComponentRef - Reference to camera component for camera info
* @example
* // Initialize debug HUD in main character
* this.DebugHUDComponent.InitializeDebugHUD(this.MovementComponent);
* @category System Setup
*/
public InitializeDebugHUD(
MovementComponentRef: AC_Movement,
ToastComponentRef: AC_ToastSystem,
InputDeviceComponentRef: AC_InputDevice,
CameraComponentRef: AC_Camera
): void {
this.MovementComponent = MovementComponentRef;
this.ToastComponent = ToastComponentRef;
this.InputDeviceComponent = InputDeviceComponentRef;
this.CameraComponent = CameraComponentRef;
this.IsInitialized = true;
this.FrameCounter = 0;
this.LastUpdateTime = 0;
this.FPS = 0;
this.RegisterDefaultPages();
this.CreateDebugWidget();
this.DebugSettings.CurrentPageIndex = 0;
this.UpdateWidgetVisibility();
this.ToastComponent.ShowToast(
'Debug HUD Initialized',
E_MessageType.Success
);
this.UpdateCurrentPage();
}
// ════════════════════════════════════════════════════════════════════════════════════════
// VARIABLES
// ════════════════════════════════════════════════════════════════════════════════════════
/**
* Reference to movement component being debugged
* Set during initialization, used for accessing movement data
* @category Components
*/
private MovementComponent: AC_Movement | null = null;
/**
* Reference to toast system component for debug messaging
* Set during initialization, used for displaying debug notifications
* @category Components
*/
public ToastComponent: AC_ToastSystem | null = null;
/**
* Reference to input device component for device detection
* Set externally, used for displaying current input device info
* @category Components
*/
public InputDeviceComponent: AC_InputDevice | null = null;
/**
* Reference to camera component for camera-related debug info
* Set externally, used for displaying camera sensitivity and state
* @category Components
*/
public CameraComponent: AC_Camera | null = null;
/**
* Debug system configuration settings
* Controls visibility, update frequency, and current page
* Instance editable for designer customization
* @category DebugConfig
* @instanceEditable true
*/
public DebugSettings: S_DebugSettings = {
CurrentMode: ESlateVisibility.Visible,
CurrentPageIndex: 0,
ShowVisualDebug: false,
UpdateFrequency: 0,
};
/**
* System initialization state flag
* Set to true after successful InitializeDebugHUD call
* @category DebugState
*/
private IsInitialized: boolean = false;
/**
* Timestamp of last HUD update (seconds)
* Used for update frequency control
* @category DebugState
*/
private LastUpdateTime: Float = 0;
/**
* Current frame counter for performance tracking
* Incremented each update cycle
* @category DebugState
*/
private FrameCounter: Float = 0;
/**
* Current frames per second calculation
* Calculated as 1.0 / DeltaTime
* @category DebugState
*/
private FPS: Float = 0;
/**
* Debug HUD widget instance
* Created during initialization, manages UI display
* @category Widget Control
*/
private DebugWidget: WBP_DebugHUD | null = null;
/**
* Array of registered debug pages
* Contains all available pages with their data and update functions
* @category Page System
*/
private DebugPages: UEArray<S_DebugPage> = new UEArray([]);
/**
* DataTable reference for debug pages storage
* Contains structured page data with Name-based indexing for efficient lookup
* @category Page System
*/
private DebugDataTable: DataTable<S_DebugPage> = DT_DebugPages;
}