558 lines
23 KiB
Markdown
558 lines
23 KiB
Markdown
[//]: # (Debug/TDD.md)
|
||
|
||
# Система 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)`** - Поиск индекса страницы по ID
|
||
- **`GetVisiblePages()`** - Получение только видимых страниц
|
||
- **`GetCurrentPage()`** - Получение активной страницы
|
||
|
||
#### Валидация
|
||
- **`IsCurrentPageValid(visiblePagesCount)`** - Проверка валидности индекса
|
||
- **`IsTimeToUpdate(timeSinceLastUpdate, updateInterval)`** - Проверка времени обновления
|
||
- **`IsAtFirstPage()`** - Проверка, является ли текущая страница первой
|
||
|
||
#### Производительность
|
||
- **`ShouldUpdateFPS(currentTime)`** - Проверка необходимости пересчета FPS
|
||
- **`UpdateFPSCounter(currentTime)`** - Расчет FPS на основе кадров
|
||
|
||
#### Виджет управление
|
||
- **`GetControlHints()`** - Получение подсказок управления по типу устройства
|
||
- **`UpdateWidgetDisplay()`** - Обновление содержимого виджета
|
||
- **`GetNavigationText()`** - Генерация текста навигации
|
||
- **`CreateDebugWidget()`** - Создание экземпляра виджета
|
||
- **`UpdateWidgetVisibility()`** - Обновление видимости виджета
|
||
- **`ShouldShowDebugHUD()`** - Проверка условий отображения HUD
|
||
|
||
### WBP_DebugHUD (UI Widget)
|
||
**Ответственности:**
|
||
- Отображение debug информации в структурированном виде
|
||
- Управление тремя текстовыми секциями: заголовок, контент, навигация
|
||
- Автоматическое обновление при изменении данных
|
||
|
||
**Ключевые функции:**
|
||
- `SetHeaderText()` - Установка заголовка текущей страницы
|
||
- `SetContentText()` - Обновление основного контента страницы
|
||
- `SetNavigationText()` - Отображение информации о навигации и FPS
|
||
|
||
### S_DebugPage (Data Structure)
|
||
**Поля:**
|
||
```typescript
|
||
{
|
||
PageID: string; // Уникальный идентификатор страницы
|
||
Title: Text; // Заголовок для отображения
|
||
Content: Text; // Текущее содержимое страницы
|
||
RefreshRate: Float; // Частота обновления (Hz)
|
||
IsVisible: boolean; // Флаг видимости
|
||
LastUpdateTime: Float; // Время последнего обновления
|
||
}
|
||
```
|
||
|
||
## Workflow использования
|
||
|
||
### Регистрация debug страницы в компоненте
|
||
|
||
```typescript
|
||
// 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);
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### Инициализация в главном персонаже
|
||
|
||
```typescript
|
||
// 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);
|
||
}
|
||
}
|
||
```
|
||
|
||
### Динамическое управление страницами
|
||
|
||
```typescript
|
||
// Добавление страницы в 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 билдах)
|
||
|
||
### ✅ Простота использования
|
||
```typescript
|
||
// Всего 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 для производительности
|
||
|
||
```typescript
|
||
// ✅ Хорошо - контролируемое обновление
|
||
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 после инициализации
|
||
- Корректность создания виджета
|
||
- Базовую функциональность регистрации страниц
|
||
|
||
**Тестовый сценарий:**
|
||
```typescript
|
||
1. Инициализация DebugHUD
|
||
2. Проверка IsInitialized == true
|
||
3. Проверка валидности компонента через SystemLibrary.IsValid()
|
||
```
|
||
|
||
### FT_DebugNavigation (Navigation System)
|
||
**Покрывает:**
|
||
- Корректность индексации при навигации
|
||
- Валидность CurrentPageIndex после NextPage/PreviousPage
|
||
- Циклическое поведение при достижении границ
|
||
- Устойчивость к многократным переходам
|
||
|
||
**Тестовый сценарий:**
|
||
```typescript
|
||
1. Инициализация с проверкой начального состояния
|
||
2. NextPage() → проверка индекса в пределах [0, VisiblePages.length)
|
||
3. PreviousPage() → проверка индекса в пределах [0, VisiblePages.length)
|
||
4. Множественные переходы → индекс всегда валидный
|
||
```
|
||
|
||
**Валидация состояния:**
|
||
```typescript
|
||
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
|
||
|
||
**Тестовый сценарий:**
|
||
```typescript
|
||
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
|
||
```typescript
|
||
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
|
||
```typescript
|
||
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
|
||
```typescript
|
||
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);
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
## Известные ограничения
|
||
|
||
### Текущие ограничения
|
||
1. **Текстовый контент только** - Нет поддержки графиков, диаграмм, интерактивных элементов
|
||
2. **Фиксированный layout** - Трехсекционный layout (header, content, navigation) не настраивается
|
||
3. **Линейная навигация** - Только последовательный переход между страницами
|
||
4. **Глобальный FPS** - Один FPS counter для всей системы
|
||
|
||
### Архитектурные решения
|
||
1. **Компоненты управляют своими страницами** - Каждый компонент отвечает за регистрацию и обновление
|
||
2. **String-based PageID** - Простота использования в ущерб типобезопасности
|
||
3. **Tick-based updates** - Компоненты обновляют страницы в своем Tick
|
||
4. **No data caching** - Контент генерируется при каждом обновлении
|
||
|
||
## Миграция со старого подхода
|
||
|
||
### Было (DT_DebugPages + E_DebugUpdateFunction)
|