529 lines
16 KiB
TypeScript
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([]);
|
|
}
|