23 KiB
23 KiB
Система Debug - Техническая Документация
Обзор
Система динамической отладки для мониторинга параметров компонентов в реальном времени. Компоненты самостоятельно регистрируют свои debug страницы и управляют их обновлением без централизованной конфигурации.
Архитектурные принципы
- Децентрализация: Каждый компонент регистрирует и обновляет свои страницы независимо
- Гибкость: Страницы могут добавляться/удаляться в runtime без предварительной конфигурации
- Производительность: Индивидуальный контроль частоты обновления для каждой страницы
- Простота: Минимум кода для добавления debug информации в любой компонент
Компоненты системы
AC_DebugHUD (Core Component)
Ответственности:
- Регистрация debug страниц от любых компонентов
- Управление навигацией и отображением страниц
- Контроль видимости debug интерфейса
- Расчет FPS и управление обновлением UI
Ключевые публичные функции:
Управление страницами
-
AddDebugPage(PageID, Title, RefreshRate, IsVisible)- Регистрирует новую debug страницу или обновляет существующую
PageID: Уникальный идентификатор (string)Title: Заголовок страницы (Text)RefreshRate: Частота обновления в Hz (number, default 30)IsVisible: Видимость страницы (boolean, default true)
-
UpdatePageContent(PageID, Content)- Обновляет содержимое страницы
- Вызывается из Tick компонента владельца страницы
Content: Текстовое содержимое (Text)
-
ShouldUpdatePage(PageID, CurrentTime)- Проверяет, нужно ли обновлять страницу согласно RefreshRate
- Возвращает
trueесли прошло достаточно времени - Автоматически обновляет LastUpdateTime при возврате
true
-
RemoveDebugPage(PageID)- Удаляет страницу из системы
- Автоматически корректирует CurrentPageIndex
-
SetPageVisibility(PageID, IsVisible)- Управляет видимостью страницы без удаления
Навигация
-
ToggleDebugHUD()- Переключает видимость всего debug интерфейса
-
NextPage()/PreviousPage()- Навигация между видимыми страницами с циклическим переходом
-
ToggleVisualDebug()- Включение/выключение визуальной отладки (debug draw)
Система
-
InitializeDebugHUD(ToastComponent, InputDeviceComponent)- Инициализация системы с опциональными компонентами
- Создание виджета и подготовка к регистрации страниц
-
UpdateHUD(CurrentTime)- Основной цикл обновления UI
- Расчет FPS и обновление отображения
- Вызывается из Tick главного персонажа
Ключевые приватные функции:
Утилиты поиска
FindPageIndex(PageID)- Поиск индекса страницы по IDGetVisiblePages()- Получение только видимых страницGetCurrentPage()- Получение активной страницы
Валидация
IsCurrentPageValid(visiblePagesCount)- Проверка валидности индексаIsTimeToUpdate(timeSinceLastUpdate, updateInterval)- Проверка времени обновленияIsAtFirstPage()- Проверка, является ли текущая страница первой
Производительность
ShouldUpdateFPS(currentTime)- Проверка необходимости пересчета FPSUpdateFPSCounter(currentTime)- Расчет FPS на основе кадров
Виджет управление
GetControlHints()- Получение подсказок управления по типу устройстваUpdateWidgetDisplay()- Обновление содержимого виджетаGetNavigationText()- Генерация текста навигацииCreateDebugWidget()- Создание экземпляра виджетаUpdateWidgetVisibility()- Обновление видимости виджетаShouldShowDebugHUD()- Проверка условий отображения HUD
WBP_DebugHUD (UI Widget)
Ответственности:
- Отображение debug информации в структурированном виде
- Управление тремя текстовыми секциями: заголовок, контент, навигация
- Автоматическое обновление при изменении данных
Ключевые функции:
SetHeaderText()- Установка заголовка текущей страницыSetContentText()- Обновление основного контента страницыSetNavigationText()- Отображение информации о навигации и FPS
S_DebugPage (Data Structure)
Поля:
{
PageID: string; // Уникальный идентификатор страницы
Title: Text; // Заголовок для отображения
Content: Text; // Текущее содержимое страницы
RefreshRate: Float; // Частота обновления (Hz)
IsVisible: boolean; // Флаг видимости
LastUpdateTime: Float; // Время последнего обновления
}
Workflow использования
Регистрация debug страницы в компоненте
// Movement/Components/AC_Movement.ts
export class AC_Movement extends ActorComponent {
private DebugHUDRef: AC_DebugHUD | null = null;
public BeginPlay(): void {
super.BeginPlay();
// Получаем ссылку на DebugHUD
this.DebugHUDRef = this.GetOwner().FindComponentByClass(AC_DebugHUD);
if (SystemLibrary.IsValid(this.DebugHUDRef)) {
// Регистрируем страницы движения
this.DebugHUDRef.AddDebugPage(
'MovementBasics', // Уникальный ID
'Movement Info', // Заголовок
30, // 30 Hz
true // Видимая
);
this.DebugHUDRef.AddDebugPage(
'MovementPhysics',
'Physics Details',
60 // 60 Hz для высокочастотных данных
);
}
}
public TickComponent(DeltaTime: Float): void {
super.TickComponent(DeltaTime);
// Обновляем свою логику
this.UpdateMovement(DeltaTime);
// Обновляем debug страницы
this.UpdateDebugPages();
}
private UpdateDebugPages(): void {
if (!SystemLibrary.IsValid(this.DebugHUDRef)) return;
const currentTime = this.GetWorld().GetTimeSeconds();
// Проверяем нужно ли обновлять страницу (учитывает RefreshRate)
if (this.DebugHUDRef.ShouldUpdatePage('MovementBasics', currentTime)) {
const content = [
`Speed: ${this.Speed.toFixed(2)} cm/s`,
`Acceleration: ${this.Acceleration.toFixed(2)} cm/s²`,
`Is Grounded: ${this.IsGrounded ? 'Yes' : 'No'}`
].join('\n');
this.DebugHUDRef.UpdatePageContent('MovementBasics', content);
}
if (this.DebugHUDRef.ShouldUpdatePage('MovementPhysics', currentTime)) {
const content = [
`Velocity: ${this.GetVelocity().Size().toFixed(2)} cm/s`,
`Mass: ${this.GetMass().toFixed(2)} kg`,
`Friction: ${this.GetFriction().toFixed(3)}`
].join('\n');
this.DebugHUDRef.UpdatePageContent('MovementPhysics', content);
}
}
}
Инициализация в главном персонаже
// Characters/BP_MainCharacter.ts
export class BP_MainCharacter extends Character {
public DebugHUDComponent: AC_DebugHUD;
public BeginPlay(): void {
super.BeginPlay();
// Инициализация DebugHUD (должна быть ПЕРВОЙ)
this.DebugHUDComponent.InitializeDebugHUD(
this.ToastSystemComponent,
this.InputDeviceComponent
);
// После этого все компоненты могут регистрировать свои страницы
}
public Tick(DeltaTime: Float): void {
super.Tick(DeltaTime);
const currentTime = this.GetGameTimeSinceCreation();
// Обновляем только UI, не контент страниц
this.DebugHUDComponent.UpdateHUD(currentTime);
}
}
Динамическое управление страницами
// Добавление страницы в runtime
public EnableAdvancedDebug(): void {
if (SystemLibrary.IsValid(this.DebugHUDRef)) {
this.DebugHUDRef.AddDebugPage(
'AdvancedMetrics',
'Advanced Metrics',
120 // Очень высокая частота
);
}
}
// Удаление страницы
public DisableAdvancedDebug(): void {
if (SystemLibrary.IsValid(this.DebugHUDRef)) {
this.DebugHUDRef.RemoveDebugPage('AdvancedMetrics');
}
}
// Скрытие/показ страницы без удаления
public TogglePhysicsDebug(): void {
if (SystemLibrary.IsValid(this.DebugHUDRef)) {
this.showPhysics = !this.showPhysics;
this.DebugHUDRef.SetPageVisibility('MovementPhysics', this.showPhysics);
}
}
Преимущества нового подхода
✅ Децентрализация
- Каждый компонент управляет своими debug страницами
- Нет необходимости модифицировать централизованные DataTable или enum'ы
- Компонент владеет логикой генерации своего debug контента
✅ Гибкость
- Страницы добавляются/удаляются динамически в runtime
- Легко менять выводимую информацию прямо в компоненте
- Условная регистрация страниц (например, только в Debug билдах)
✅ Простота использования
// Всего 3 шага:
// 1. Регистрация в BeginPlay
this.DebugHUD.AddDebugPage('MyPage', 'My Title', 30);
// 2. Проверка в Tick
if (this.DebugHUD.ShouldUpdatePage('MyPage', currentTime)) {
// 3. Обновление контента
this.DebugHUD.UpdatePageContent('MyPage', this.GetDebugText());
}
✅ Индивидуальный контроль производительности
- Каждая страница имеет свой RefreshRate
- Критичные данные: 60-120 Hz
- Обычные данные: 30 Hz
- Статичные данные: 15 Hz или меньше
✅ Blueprint-совместимость
- Все параметры - простые типы (string, Text, number, boolean)
- Нет callback'ов или сложных структур данных
- Можно использовать как из C++/TypeScript, так и из Blueprint
Performance considerations
Оптимизации
- Smart update timing:
ShouldUpdatePage()автоматически контролирует частоту - Single widget update: UpdateHUD обновляет только текущую видимую страницу
- Lazy evaluation: Контент генерируется только когда страница видима и нужно обновление
- FPS calculation: Раз в секунду, не влияет на gameplay
Benchmarks
- AddDebugPage: <0.1ms (простое добавление в массив)
- UpdatePageContent: <0.05ms (обновление одного элемента массива)
- ShouldUpdatePage: <0.05ms (простая проверка времени)
- UpdateHUD (widget refresh): <0.2ms (обновление UI элементов)
- Memory per page: ~200 bytes (структура + strings)
Best Practices для производительности
// ✅ Хорошо - контролируемое обновление
if (this.DebugHUD.ShouldUpdatePage('MyPage', currentTime)) {
this.DebugHUD.UpdatePageContent('MyPage', this.BuildContent());
}
// ✅ Хорошо - разная частота для разных данных
this.DebugHUD.AddDebugPage('CriticalData', 'Critical', 60); // Частое
this.DebugHUD.AddDebugPage('GeneralInfo', 'General', 30); // Обычное
this.DebugHUD.AddDebugPage('StaticData', 'Static', 5); // Редкое
// ❌ Плохо - обновление без проверки частоты
this.DebugHUD.UpdatePageContent('MyPage', content); // Каждый кадр!
// ❌ Плохо - слишком высокая частота для некритичных данных
this.DebugHUD.AddDebugPage('SlowData', 'Slow', 120); // Избыточно
Система тестирования
FT_DebugSystem (Basic Functionality)
Покрывает:
- Успешность инициализации системы (
IsInitialized = true) - Валидность компонента DebugHUD после инициализации
- Корректность создания виджета
- Базовую функциональность регистрации страниц
Тестовый сценарий:
1. Инициализация DebugHUD
2. Проверка IsInitialized == true
3. Проверка валидности компонента через SystemLibrary.IsValid()
FT_DebugNavigation (Navigation System)
Покрывает:
- Корректность индексации при навигации
- Валидность CurrentPageIndex после NextPage/PreviousPage
- Циклическое поведение при достижении границ
- Устойчивость к многократным переходам
Тестовый сценарий:
1. Инициализация с проверкой начального состояния
2. NextPage() → проверка индекса в пределах [0, VisiblePages.length)
3. PreviousPage() → проверка индекса в пределах [0, VisiblePages.length)
4. Множественные переходы → индекс всегда валидный
Валидация состояния:
private IsStateValid(): boolean {
const { VisiblePagesLength, CurrentPageIndex } = this.DebugHUD.GetTestData();
return (
VisiblePagesLength > 0 &&
CurrentPageIndex >= 0 &&
CurrentPageIndex < VisiblePagesLength
);
}
FT_DebugPageManagement (NEW - Page Operations)
Покрывает:
- Динамическое добавление страниц через AddDebugPage
- Обновление контента через UpdatePageContent
- Проверку частоты обновления через ShouldUpdatePage
- Удаление страниц через RemoveDebugPage
- Управление видимостью через SetPageVisibility
Тестовый сценарий:
1. AddDebugPage('TestPage1', 'Test', 30)
→ Проверка что страница добавлена (DebugPages.length == 1)
2. UpdatePageContent('TestPage1', 'New Content')
→ Проверка что контент обновился
3. ShouldUpdatePage('TestPage1', time)
→ Проверка что возвращает true при первом вызове
→ Проверка что возвращает false сразу после
4. AddDebugPage('TestPage2', 'Test2', 60)
→ Проверка что страница добавлена (DebugPages.length == 2)
5. SetPageVisibility('TestPage2', false)
→ Проверка что VisiblePages.length == 1
6. RemoveDebugPage('TestPage1')
→ Проверка что страница удалена (DebugPages.length == 1)
→ Проверка что CurrentPageIndex корректно обновился
Структура файлов
Content/
├── Debug/
│ ├── Components/
│ │ └── AC_DebugHUD.ts # Main debug system component
│ ├── Structs/
│ │ └── S_DebugPage.ts # Page data structure
│ ├── UI/
│ │ └── WBP_DebugHUD.ts # Debug HUD widget
│ └── Tests/
│ ├── FT_DebugSystem.ts # Basic functionality tests
│ ├── FT_DebugNavigation.ts # Navigation system tests
│ └── FT_DebugPageManagement.ts # Page operations tests (NEW)
├── Input/
│ └── IMC_Default.ts # Input mapping integration
└── Characters/
└── BP_MainCharacter.ts # Main integration point
Примеры использования из разных компонентов
Camera Component Debug
export class AC_Camera extends ActorComponent {
private DebugHUDRef: AC_DebugHUD | null = null;
public BeginPlay(): void {
super.BeginPlay();
this.DebugHUDRef = this.GetOwner().FindComponentByClass(AC_DebugHUD);
if (SystemLibrary.IsValid(this.DebugHUDRef)) {
this.DebugHUDRef.AddDebugPage(
'CameraInfo',
'Camera State',
30
);
}
}
public TickComponent(DeltaTime: Float): void {
super.TickComponent(DeltaTime);
if (!SystemLibrary.IsValid(this.DebugHUDRef)) return;
const currentTime = this.GetWorld().GetTimeSeconds();
if (this.DebugHUDRef.ShouldUpdatePage('CameraInfo', currentTime)) {
const content = [
`FOV: ${this.GetFOV().toFixed(1)}°`,
`Distance: ${this.GetCameraDistance().toFixed(2)} cm`,
`Pitch: ${this.GetPitch().toFixed(1)}°`,
`Yaw: ${this.GetYaw().toFixed(1)}°`,
`Target: ${this.GetTargetLocation().ToString()}`
].join('\n');
this.DebugHUDRef.UpdatePageContent('CameraInfo', content);
}
}
}
Network Component Debug
export class AC_NetworkReplication extends ActorComponent {
private DebugHUDRef: AC_DebugHUD | null = null;
public BeginPlay(): void {
super.BeginPlay();
this.DebugHUDRef = this.GetOwner().FindComponentByClass(AC_DebugHUD);
if (SystemLibrary.IsValid(this.DebugHUDRef)) {
// Регистрируем только в сетевой игре
if (this.GetWorld().IsNetMode()) {
this.DebugHUDRef.AddDebugPage(
'NetworkStats',
'Network Statistics',
15 // Обновляем реже для сетевых данных
);
}
}
}
public TickComponent(DeltaTime: Float): void {
super.TickComponent(DeltaTime);
if (!SystemLibrary.IsValid(this.DebugHUDRef)) return;
const currentTime = this.GetWorld().GetTimeSeconds();
if (this.DebugHUDRef.ShouldUpdatePage('NetworkStats', currentTime)) {
const content = [
`Ping: ${this.GetPing()}ms`,
`Packet Loss: ${this.GetPacketLoss().toFixed(2)}%`,
`Bandwidth: ${this.GetBandwidth().toFixed(1)} KB/s`,
`Connected: ${this.IsConnected() ? 'Yes' : 'No'}`,
`Players: ${this.GetPlayerCount()}`
].join('\n');
this.DebugHUDRef.UpdatePageContent('NetworkStats', content);
}
}
}
Conditional Debug Pages
export class AC_AdvancedSystem extends ActorComponent {
private DebugHUDRef: AC_DebugHUD | null = null;
private showDetailedDebug: boolean = false;
public BeginPlay(): void {
super.BeginPlay();
this.DebugHUDRef = this.GetOwner().FindComponentByClass(AC_DebugHUD);
if (SystemLibrary.IsValid(this.DebugHUDRef)) {
// Базовая страница всегда
this.DebugHUDRef.AddDebugPage('BasicInfo', 'Basic Info', 30);
// Детальная только в Debug билде
if (BUILD_DEBUG) {
this.DebugHUDRef.AddDebugPage(
'DetailedInfo',
'Detailed Debug',
120,
false // Скрыта по умолчанию
);
}
}
}
public ToggleDetailedDebug(): void {
this.showDetailedDebug = !this.showDetailedDebug;
if (SystemLibrary.IsValid(this.DebugHUDRef)) {
this.DebugHUDRef.SetPageVisibility('DetailedInfo', this.showDetailedDebug);
}
}
}
Известные ограничения
Текущие ограничения
- Текстовый контент только - Нет поддержки графиков, диаграмм, интерактивных элементов
- Фиксированный layout - Трехсекционный layout (header, content, navigation) не настраивается
- Линейная навигация - Только последовательный переход между страницами
- Глобальный FPS - Один FPS counter для всей системы
Архитектурные решения
- Компоненты управляют своими страницами - Каждый компонент отвечает за регистрацию и обновление
- String-based PageID - Простота использования в ущерб типобезопасности
- Tick-based updates - Компоненты обновляют страницы в своем Tick
- No data caching - Контент генерируется при каждом обновлении