// Debug/Components/AC_DebugHUD.ts import type { S_DebugPage } from '#root/Debug/Structs/S_DebugPage.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_ToastSystem } from '#root/Toasts/Components/AC_ToastSystem.ts'; import { ActorComponent } from '#root/UE/ActorComponent.ts'; import { CreateWidget } from '#root/UE/CteateWidget.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 // ════════════════════════════════════════════════════════════════════════════════════════ /** * Add or register a debug page in the system * @param PageID - Unique identifier for the page * @param Title - Display title for the page * @param RefreshRate - How often this page should be updated (Hz), default 30 * @param IsVisible - Whether the page is visible by default, default true * @example * // In your component's BeginPlay: * this.DebugHUDRef.AddDebugPage('MovementInfo', 'Movement Data', 30); * @category Page Management */ public AddDebugPage( PageID: string, Title: Text, RefreshRate: number = 30, IsVisible: boolean = true ): void { const existingPageIndex = this.FindPageIndex(PageID); const pageData: S_DebugPage = { PageID, Title, Content: '', RefreshRate, IsVisible, LastUpdateTime: 0, }; if (existingPageIndex >= 0) { this.DebugPages.SetArrayElem(existingPageIndex, { ...pageData, Content: this.DebugPages.Get(existingPageIndex).Content, }); } else { this.DebugPages.Add(pageData); } } /** * Update content of a specific debug page * Call this from your component's Tick or custom update function * @param PageId - Unique identifier of the page to update * @param Content - New content text for the page * @example * // In your component's Tick: * this.DebugHUDRef.UpdatePageContent( * 'MovementInfo', * `Speed: ${this.Speed}\nAcceleration: ${this.Acceleration}` * ); * @category Page Management */ public UpdatePageContent(PageId: string, Content: Text): void { const pageIndex = this.FindPageIndex(PageId); if (pageIndex >= 0) { this.DebugPages.SetArrayElem(pageIndex, { ...this.DebugPages.Get(pageIndex), Content, }); } } /** * Check if page needs update based on its refresh rate * Use this to control update frequency in your component * @param PageID - Page identifier to check * @param CurrentTime - Current game time * @returns True if page should be updated * @example * // In your component's Tick: * if (this.DebugHUDRef.ShouldUpdatePage('MovementInfo', currentTime)) { * this.DebugHUDRef.UpdatePageContent('MovementInfo', this.GetDebugContent()); * } * @category Page Management * @pure true */ public ShouldUpdatePage(PageID: string, CurrentTime: Float): boolean { const pageIndex = this.FindPageIndex(PageID); if (pageIndex >= 0) { let page = this.DebugPages.Get(pageIndex); const IsTimeToUpdate = ( currentTime: Float, refreshRate: Float, updateTime: Float ): boolean => currentTime - updateTime >= 1 / refreshRate; const { RefreshRate: refreshRate, LastUpdateTime: updateTime } = page; if (IsTimeToUpdate(CurrentTime, refreshRate, updateTime)) { page = { ...page, LastUpdateTime: CurrentTime, }; this.DebugPages.SetArrayElem(pageIndex, page); return true; } else { return false; } } else { return false; } } /** * Remove a debug page from the system * @param pageId - Unique identifier of the page to remove * @category Page Management */ public RemoveDebugPage(pageId: string): void { const pageIndex = this.FindPageIndex(pageId); if (pageIndex >= 0) { this.DebugPages.RemoveIndex(pageIndex); // Adjust current page index if needed if (this.CurrentPageIndex >= this.GetVisiblePages().length) { this.CurrentPageIndex = Math.max(0, this.GetVisiblePages().length - 1); } } } /** * Set visibility of a specific debug page * @param pageId - Page identifier * @param isVisible - Visibility state * @category Page Management */ public SetPageVisibility(pageId: string, isVisible: boolean): void { const pageIndex = this.FindPageIndex(pageId); if (pageIndex >= 0) { this.DebugPages.SetArrayElem(pageIndex, { ...this.DebugPages.Get(pageIndex), IsVisible: isVisible, }); } } /** * Get all currently visible debug pages * @returns Array of visible pages only * @category Page Management * @pure true */ private GetVisiblePages(): UEArray { const filteredArray: UEArray = new UEArray([]); this.DebugPages.forEach(page => { if (page.IsVisible) { filteredArray.Add(page); } }); return filteredArray; } /** * Get currently selected debug page * @returns Current page data or null if invalid * @category Page Management * @pure true */ private GetCurrentPage(): { Page: S_DebugPage | null; IsFound: boolean } { const IsCurrentPageValid = (length: Integer): boolean => length === 0 || this.CurrentPageIndex >= length; if (!IsCurrentPageValid(this.GetVisiblePages().length)) { return { Page: this.GetVisiblePages().Get(this.CurrentPageIndex), IsFound: true, }; } return { Page: null, IsFound: false }; } /** * Toggle debug HUD visibility between visible and hidden * @category Navigation */ public ToggleDebugHUD(): void { if (SystemLibrary.IsValid(this.DebugWidget)) { this.CurrentMode = this.CurrentMode === ESlateVisibility.Visible ? ESlateVisibility.Hidden : ESlateVisibility.Visible; this.DebugWidget.SetVisibility(this.CurrentMode); } } /** * 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 isAtFirstPage = (): boolean => this.CurrentPageIndex - 1 < 0; const getLastPageIndex = (): Integer => length - 1; const getPreviousPageIndex = (): Integer => this.CurrentPageIndex - 1; this.CurrentPageIndex = isAtFirstPage() ? getLastPageIndex() : getPreviousPageIndex(); } } /** * Navigate to next debug page * Wraps around to first page if at end * @category Navigation */ public NextPage(): void { const length = this.GetVisiblePages().length; const GetNextPageIndex = (): Integer => (this.CurrentPageIndex + 1) % length; if (length > 1) { this.CurrentPageIndex = GetNextPageIndex(); } } /** * Toggle visual debug rendering (collision shapes, rays, etc.) * @category Visual Debug */ public ToggleVisualDebug(): void { this.ShowVisualDebug = !this.ShowVisualDebug; if (SystemLibrary.IsValid(this.ToastComponent)) { this.ToastComponent.ShowToast( `Visual Debug ${this.ShowVisualDebug ? 'Enabled' : 'Disabled'}` ); } } /** * Get comprehensive test data for debug system validation * @returns Object containing initialization status, DataTable reference, and pages array * @category Testing */ public GetTestData(): { IsInitialized: boolean; DebugPages: UEArray; VisiblePagesLength: Integer; CurrentPageIndex: Integer; } { return { IsInitialized: this.IsInitialized, DebugPages: this.DebugPages, VisiblePagesLength: this.GetVisiblePages().length, CurrentPageIndex: this.CurrentPageIndex, }; } /** * Initialize debug HUD system with movement component reference * Sets up pages, creates widget, runs tests, and starts display * @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( ToastComponentRef: AC_ToastSystem | null, InputDeviceComponentRef: AC_InputDevice | null ): void { this.ToastComponent = ToastComponentRef; this.InputDeviceComponent = InputDeviceComponentRef; if (!this.IsInitialized) { this.IsInitialized = true; this.CreateDebugWidget(); if (SystemLibrary.IsValid(this.ToastComponent)) { this.ToastComponent.ShowToast( 'Debug HUD Initialized', E_MessageType.Success ); } } } /** * Find index of page by ID * @param PageId - Page identifier to search for * @returns Index of the page or -1 if not found * @category Utility */ private FindPageIndex(PageId: string): Integer { for (let i = 0; i < this.DebugPages.length; i++) { if (this.DebugPages.Get(i).PageID === PageId) { return i; } } return -1; } /** * Main update loop for debug HUD system * @param CurrentTime - Current game time in seconds * @category HUD Rendering */ public UpdateHUD(CurrentTime: Float): void { if (this.IsInitialized && SystemLibrary.IsValid(this.DebugWidget)) { this.FrameCounter++; const ShouldUpdateFPS = (currentTime: Float): boolean => currentTime - this.LastUpdateTime >= 1; const UpdateFPSCounter = (currentTime: Float): Float => this.FrameCounter / (currentTime - this.LastUpdateTime); if (ShouldUpdateFPS(CurrentTime)) { this.FPS = UpdateFPSCounter(CurrentTime); this.FrameCounter = 0; this.LastUpdateTime = CurrentTime; if (this.ShouldShowDebugHUD()) { this.UpdateWidgetDisplay(); } } } } /** * Get navigation instructions based on input device * @return Control hints text * @category Widget Management * @pure true */ private GetControlHints(): Text { if (SystemLibrary.IsValid(this.InputDeviceComponent)) { if (this.InputDeviceComponent.IsGamepad()) { return 'LT+RT+Right/LT+RT+Left'; } } return 'PageUp/PageDown'; } /** * Update widget display with current page content * @category Widget Management */ private UpdateWidgetDisplay(): void { const { Page: currentPage, IsFound } = this.GetCurrentPage(); if (IsFound && SystemLibrary.IsValid(this.DebugWidget)) { this.DebugWidget.SetHeaderText(currentPage!.Title); this.DebugWidget.SetContentText(currentPage!.Content); this.DebugWidget.SetNavigationText(this.GetNavigationText()); } } /** * Generate navigation text showing current page position * @returns Formatted navigation string * @category Widget Control * @pure true */ private GetNavigationText(): string { return `Page ${this.CurrentPageIndex + 1}/${this.GetVisiblePages().length} | FPS: ${this.FPS} | ${this.GetControlHints()}`; } /** * Create debug widget instance and add to viewport * @category Widget Control */ private CreateDebugWidget(): void { this.DebugWidget = CreateWidget(WBP_DebugHUD); if (SystemLibrary.IsValid(this.DebugWidget)) { this.DebugWidget.AddToViewport(); this.UpdateWidgetVisibility(); } else { if (SystemLibrary.IsValid(this.ToastComponent)) { this.ToastComponent.ShowToast( 'Failed to create debug widget', E_MessageType.Error ); } } } /** * 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 ); } } /** * 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 { return ( this.CurrentMode === ESlateVisibility.Visible && this.GetVisiblePages().length > 0 ); } // ════════════════════════════════════════════════════════════════════════════════════════ // VARIABLES // ════════════════════════════════════════════════════════════════════════════════════════ /** * 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; /** * Current mode of the debug HUD (Visible, Hidden, Collapsed) * @category Debug Config * @instanceEditable true */ public CurrentMode: ESlateVisibility = ESlateVisibility.Visible; /** * Index of the currently displayed debug page * @category Debug Config */ public CurrentPageIndex: Integer = 0; /** * Visual debug rendering toggle (collision shapes, rays, etc.) * @category Debug Config * @instanceEditable true */ public ShowVisualDebug: boolean = true; /** * 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([]); }