tengri/Content/Debug/Components/AC_DebugHUD.ts

529 lines
16 KiB
TypeScript

// 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<S_DebugPage> {
const filteredArray: UEArray<S_DebugPage> = 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<S_DebugPage>;
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<S_DebugPage> = new UEArray([]);
}