tengri/Content/Debug/Components/AC_DebugHUD.ts

632 lines
19 KiB
TypeScript

// Content/Debug/Components/AC_DebugHUD.ts
import type {AC_Movement} from "../../Movement/Components/AC_Movement.js";
import type {Float, Integer} from "../../types.js";
import type {S_DebugPage} from "../Structs/S_DebugPage.js";
import {AddToArray, CreateWidget, GetFromArray, IsValid, Print, SetArrayElem} from "../../functions.js";
import {E_DebugPageID} from "../Enums/E_DebugPageID.js";
import {E_DebugUpdateFunction} from "../Enums/E_DebugUpdateFunction.js";
import {WBP_DebugHUD} from "../UI/WBP_DebugHUD.js";
import type {S_DebugSettings} from "../Structs/S_DebugSettings.js";
import {E_DebugMode} from "../Enums/E_DebugMode.js";
import {ESlateVisibility} from "../../enums.js";
/**
* Debug HUD Controller Component
* Manages debug information display system for deterministic movement
* Provides real-time performance monitoring and parameter visualization
* Part of Stage 2: Debug HUD system implementation
*/
export class AC_DebugHUD {
// Category: "DebugConfig"
// Instance Editable: true
/**
* Debug system configuration settings
* Controls visibility, update frequency, and current page
* Instance editable for designer customization
*/
public DebugSettings: S_DebugSettings = {
CurrentMode: E_DebugMode.Visible,
CurrentPageIndex: 0,
ShowVisualDebug: false,
UpdateFrequency: 0.0,
}
// Category: "DebugState"
/**
* System initialization state flag
* Set to true after successful InitializeDebugHUD call
*/
private IsInitialized: boolean = false;
// Category: "DebugState"
/**
* Timestamp of last HUD update (seconds)
* Used for update frequency control
*/
private LastUpdateTime: Float = 0;
// Category: "DebugState"
/**
* Current frame counter for performance tracking
* Incremented each update cycle
*/
private FrameCounter: Float = 0;
// Category: "DebugState"
/**
* Current frames per second calculation
* Calculated as 1.0 / DeltaTime
*/
private FPS: Float = 0;
// Category: "DebugState"
/**
* Reference to movement component being debugged
* Set during initialization, used for accessing movement data
*/
private MovementComponent: AC_Movement | null = null;
// Category: "Widget Control"
/**
* Debug HUD widget instance
* Created during initialization, manages UI display
*/
private DebugWidget: WBP_DebugHUD | null = null;
// Category: "Page System"
/**
* Array of registered debug pages
* Contains all available pages with their data and update functions
*/
private DebugPages: S_DebugPage[] = [];
// Category: "HUD Control"
// Pure: true
/**
* Check if debug HUD should be visible
* @returns True if system is initialized and mode is visible
* @pure Function has no side effects
*/
private ShouldShowDebugHUD() {
return this.IsInitialized && this.DebugSettings.CurrentMode === E_DebugMode.Visible;
}
// Category: "HUD Control"
// Pure: true
/**
* Check if debug HUD should update this frame
* @param CurrentTime - Current game time in seconds
* @returns True if enough time has passed since last update
* @pure Function has no side effects
* @example
* // Update every frame (UpdateFrequency = 0)
* ShouldUpdateDebugHUD(gameTime) // returns true
* // Update at 30Hz (UpdateFrequency = 30)
* ShouldUpdateDebugHUD(gameTime) // returns true every 1/30 seconds
*/
private ShouldUpdateDebugHUD(CurrentTime: Float): boolean {
if (this.DebugSettings.UpdateFrequency <= 0) {
return true; // Update every frame
} else {
return (CurrentTime - this.LastUpdateTime) >= (1.0 / this.DebugSettings.UpdateFrequency);
}
}
// Category: "Page Management"
/**
* Register or update a debug page in the system
* @param PageData - Page configuration and content data
* @private Internal page management method
* @example
* // Register movement constants page
* RegisterDebugPage({
* PageID: E_DebugPageID.MovementInfo,
* Title: "Movement Constants",
* Content: "",
* IsVisible: true,
* UpdateFunction: E_DebugUpdateFunction.UpdateMovementPage
* })
*/
private RegisterDebugPage(PageData: S_DebugPage): void {
let existingIndex: Integer = -1;
this.DebugPages.forEach((page, index) => {
if (page.PageID === PageData.PageID) {
this.DebugPages[index] = PageData;
return;
}
})
if (existingIndex >= 0) {
SetArrayElem(this.DebugPages, existingIndex, PageData);
} else{
AddToArray(this.DebugPages, PageData);
}
}
// Category: "Page Management"
// Pure: true
/**
* Get all currently visible debug pages
* @returns Array of visible pages only
* @pure Function has no side effects
*/
private GetVisiblePages(): S_DebugPage[] {
const filteredPages: S_DebugPage[] = [];
this.DebugPages.forEach((page) => {
if (page.IsVisible) {
AddToArray(filteredPages, page);
}
})
return filteredPages;
}
// Category: "Page Management"
/**
* Get currently selected debug page
* @returns Object with page data and validity flag
* @pure Function has no side effects
*/
private GetCurrentPage(): {Page: S_DebugPage | null, IsFound: boolean} {
const length = this.GetVisiblePages().length;
if (!((length === 0) || (this.DebugSettings.CurrentPageIndex >= length))) {
return {Page: GetFromArray(this.DebugPages, this.DebugSettings.CurrentPageIndex), IsFound: true};
} else {
return {Page: null, IsFound: false};
}
}
// Category: "Navigation"
/**
* Toggle debug HUD visibility between visible and hidden
* @public User input handler for F1 key or similar
*/
public ToggleDebugHUD() {
this.DebugSettings.CurrentMode = this.DebugSettings.CurrentMode === E_DebugMode.Visible ? E_DebugMode.Hidden : E_DebugMode.Visible;
}
// Category: "Navigation"
/**
* Navigate to previous debug page
* Wraps around to last page if at beginning
* @public User input handler for PageUp key
*/
public PreviousPage() {
const length = this.GetVisiblePages().length;
if (length > 1) {
const currentPage = this.DebugSettings.CurrentPageIndex;
this.DebugSettings.CurrentPageIndex = currentPage - 1 < 0 ? length - 1 : currentPage - 1;
}
}
// Category: "Navigation"
/**
* Navigate to next debug page
* Wraps around to first page if at end
* @public User input handler for PageDown key
*/
public NextPage() {
const length = this.GetVisiblePages().length;
if (length > 1) {
this.DebugSettings.CurrentPageIndex = (this.DebugSettings.CurrentPageIndex + 1) % length;
}
}
// Category: "Visual Debug"
/**
* Toggle visual debug rendering (collision shapes, rays, etc.)
* @public User input handler for visual debug toggle
*/
public ToggleVisualDebug() {
this.DebugSettings.ShowVisualDebug = !this.DebugSettings.ShowVisualDebug;
}
// Category: "Page Updates"
/**
* Update content of currently selected page
* Calls appropriate update function based on page type
* @private Internal update system method
*/
private UpdateCurrentPage() {
const {Page, IsFound}: {Page: S_DebugPage, IsFound: boolean} = this.GetCurrentPage();
let CurrentPage = Page;
if (IsFound) {
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;
}
SetArrayElem(this.DebugPages, this.DebugSettings.CurrentPageIndex, CurrentPage);
this.UpdateWidgetPage();
}
}
// Category: "Page Updates"
/**
* Update movement constants page content
* @param Page - Page structure to update
* @returns Updated page with current movement data
* @private Page-specific update method
*/
private UpdateMovementPage(Page: S_DebugPage): S_DebugPage {
if (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
}
}
}
// Category: "Page Updates"
/**
* Update surface classification page content
* @param Page - Page structure to update
* @returns Updated page with current surface angle thresholds
* @private Page-specific update method
*/
private UpdateSurfacePage(Page: S_DebugPage): S_DebugPage {
if (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
}
}
}
// Category: "Page Updates"
/**
* Update performance metrics page content
* @param Page - Page structure to update
* @returns Updated page with current performance data
* @private Page-specific update method
*/
private UpdatePerformancePage(Page: S_DebugPage): S_DebugPage {
if (IsValid(this.MovementComponent)) {
return {
PageID: Page.PageID,
Title: Page.Title,
Content: `Frame: ${this.FrameCounter}\n` +
`FPS: ${this.FPS}\n` +
`Update Rate: ${this.DebugSettings.UpdateFrequency <= 0 ? '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
}
}
}
// Category: "Widget Control"
/**
* Create debug widget instance and add to viewport
* @private Internal widget management method
*/
private CreateDebugWidget() {
this.DebugWidget = CreateWidget<WBP_DebugHUD>(new WBP_DebugHUD);
this.DebugWidget.MovementComponent = this.MovementComponent;
if (IsValid(this.DebugWidget)) {
this.DebugWidget.AddToViewport();
} else {
Print('Failed to create debug widget');
}
}
// Category: "Widget Control"
/**
* Update widget visibility based on current debug mode
* @private Internal widget management method
*/
private UpdateWidgetVisibility() {
if (IsValid(this.DebugWidget)) {
this.DebugWidget.SetVisibility(this.ShouldShowDebugHUD() ? ESlateVisibility.Visible : ESlateVisibility.Hidden);
}
}
// Category: "Widget Communication"
/**
* Send current page data to debug widget for display
* @private Internal widget communication method
*/
private UpdateWidgetPage() {
const {Page, IsFound} = this.GetCurrentPage();
if (IsFound) {
this.DebugWidget.SetHeaderText(Page.Title);
this.DebugWidget.SetContentText(Page.Content);
this.DebugWidget.SetNavigationText(`Page ${this.DebugSettings.CurrentPageIndex + 1}/${this.GetVisiblePages().length} | PageUp/PageDown - Navigate`);
}
}
// Category: "HUD Rendering"
/**
* 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
* @public Called by BP_MainCharacter or game framework
*/
public UpdateHUD(CurrentTime: Float, DeltaTime: Float) {
if (this.IsInitialized && this.ShouldUpdateDebugHUD(CurrentTime)) {
this.FrameCounter++;
this.FPS = DeltaTime > 0.0 ? 1.0 / DeltaTime : 0.0;
this.LastUpdateTime = CurrentTime;
if (this.ShouldShowDebugHUD()) {
this.UpdateCurrentPage();
this.UpdateWidgetPage();
}
}
}
// Category: "System Setup"
/**
* Register default debug pages (Movement, Surface, Performance)
* @private Internal system setup method
*/
private RegisterDefaultPages() {
this.RegisterDebugPage({
PageID: E_DebugPageID.MovementInfo,
Title: "Movement Constants",
Content: '',
IsVisible: true,
UpdateFunction: E_DebugUpdateFunction.UpdateMovementPage
});
this.RegisterDebugPage({
PageID: E_DebugPageID.SurfaceInfo,
Title: "Surface Classification",
Content: '',
IsVisible: true,
UpdateFunction: E_DebugUpdateFunction.UpdateSurfacePage
});
this.RegisterDebugPage({
PageID: E_DebugPageID.PerformanceInfo,
Title: "Performance Metrics",
Content: '',
IsVisible: true,
UpdateFunction: E_DebugUpdateFunction.UpdatePerformancePage
});
}
// Category: "Testing"
/**
* Test basic debug system functionality
* @returns True if all basic tests pass
* @private Internal testing method
*/
private TestDebugSystem() {
Print('=== DEBUG SYSTEM TESTS ===');
if (this.IsInitialized) {
Print('✅ System initialized');
if (this.DebugPages.length === 3) {
Print('✅ All pages registered');
if (IsValid(this.MovementComponent)) {
Print('✅ Movement component valid');
if (IsValid(this.DebugWidget)) {
Print('✅ Debug widget created');
Print('✅ ALL TESTS PASSED');
return true;
} else {
Print('❌ Debug widget not created');
return false;
}
} else {
Print('❌ Movement component not valid');
return false;
}
} else {
Print(`❌ Expected 3 pages, got ${this.DebugPages.length}`);
return false;
}
} else {
Print('❌ System not initialized');
return false;
}
}
// Category: "Testing"
/**
* Test page content generation for all registered pages
* @returns True if all pages generate non-empty content
* @private Internal testing method
*/
private TestPageContentGeneration() {
let allTestsPassed = true;
this.DebugPages.forEach((page, index) => {
let updatedPage: S_DebugPage = page;
switch (updatedPage.UpdateFunction) {
case E_DebugUpdateFunction.UpdateMovementPage:
updatedPage = this.UpdateMovementPage(updatedPage);
break;
case E_DebugUpdateFunction.UpdateSurfacePage:
updatedPage = this.UpdateSurfacePage(updatedPage);
break;
case E_DebugUpdateFunction.UpdatePerformancePage:
updatedPage = this.UpdatePerformancePage(updatedPage);
break;
}
if (updatedPage.Content.length > 0) {
Print(`✅ Page ${index + 1} content generated`);
} else {
Print(`❌ Page ${index + 1} content empty`);
allTestsPassed = false;
return;
}
})
return allTestsPassed;
}
// Category: "Testing"
// Pure: true
/**
* Validate current navigation state
* @returns True if current page index is valid
* @pure Function has no side effects
*/
private IsValidNavigationState() {
if (this.DebugSettings.CurrentPageIndex >= 0) {
const visiblePagesLength = this.GetVisiblePages().length;
return !((visiblePagesLength > 0) && (this.DebugSettings.CurrentPageIndex >= visiblePagesLength));
} else {
return false;
}
}
// Category: "Testing"
/**
* Test page navigation functionality
* @param StartCurrentPage - Initial page index to restore after test
* @returns True if navigation works correctly
* @private Internal testing method
*/
private TestNavigation(StartCurrentPage: Integer) {
if (this.IsValidNavigationState()) {
if (this.IsValidNavigationState()) {
this.NextPage();
if (this.IsValidNavigationState()) {
if (this.IsValidNavigationState()) {
this.PreviousPage();
if (this.IsValidNavigationState()) {
this.DebugSettings.CurrentPageIndex = StartCurrentPage;
if (this.IsValidNavigationState()) {
return true;
} else {
Print('❌ Navigation: Failed to restore valid state');
return false;
}
} else {
Print('❌ Navigation: PrevPage failed — State became invalid after PreviousPage');
return false;
}
} else {
Print('❌ Navigation: PreviousPage failed — Invalid state before PreviousPage');
return false;
}
} else {
Print('❌ Navigation: NextPage failed — State became invalid after NextPage');
return false;
}
} else {
Print('❌ Navigation: NextPage failed — Invalid state before NextPage');
return false;
}
} else {
Print('❌ Navigation: Invalid initial state');
return false;
}
}
// Category: "Testing"
/**
* Run comprehensive test suite for debug system
* Tests initialization, page content generation, and navigation
* @private Called during initialization for validation
*/
private RunAllTests() {
Print('=== COMPREHENSIVE DEBUG SYSTEM TESTING ===');
if (this.TestDebugSystem()) {
if (this.TestPageContentGeneration()) {
if (this.TestNavigation(this.DebugSettings.CurrentPageIndex)) {
Print('✅ ALL TESTS PASSED');
} else {
Print('❌ Navigation Tests: Failed');
}
} else {
Print('❌ Page Content Generation Tests: Failed');
}
} else {
Print('❌ System Tests: Failed');
}
}
// Category: "System Setup"
/**
* 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
* @public Called by BP_MainCharacter during initialization
* @example
* // Initialize debug HUD in main character
* this.DebugHUDComponent.InitializeDebugHUD(this.MovementComponent);
*/
public InitializeDebugHUD(MovementComponentRef: AC_Movement) {
this.MovementComponent = MovementComponentRef;
this.IsInitialized = true;
this.FrameCounter = 0;
this.LastUpdateTime = 0;
this.FPS = 0;
this.RegisterDefaultPages();
this.CreateDebugWidget();
this.RunAllTests();
this.DebugSettings.CurrentPageIndex = 0;
this.UpdateWidgetVisibility();
Print('=== DEBUG HUD INITIALIZED ===');
this.UpdateCurrentPage();
}
}