tengri/Content/Debug/TDD.md

558 lines
23 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

[//]: # (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)