// Debug/Components/AC_DebugHUD.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 { 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 { const filteredPages: UEArray = 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); } } 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: `Max Speed: ${this.MovementComponent.MovementConstants.MaxSpeed}\n` + `Acceleration: ${this.MovementComponent.MovementConstants.Acceleration}\n` + `Friction: ${this.MovementComponent.MovementConstants.Friction}\n` + `Gravity: ${this.MovementComponent.MovementConstants.Gravity}`, 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, }; } } /** * 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; DebugPages: UEArray; } { 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 * @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 ): void { this.MovementComponent = MovementComponentRef; this.ToastComponent = ToastComponentRef; this.InputDeviceComponent = InputDeviceComponentRef; 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; /** * 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 = 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 = DT_DebugPages; }