aboutsummaryrefslogtreecommitdiff
path: root/docs/tomes/02-architecture.md
blob: b44bd51de9e8e8ca21fc5350281ec3ff413a729d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
# II. Запуск, архитектура и игровой цикл

Этот том описывает путь от запуска `iron_3d.exe` до устойчивого кадра:
загрузку `iron3d.dll`, создание shell/game objects, поднятие платформенных
сервисов, запуск World3D, расчёт simulation step, безопасное удаление объектов,
рендер и завершение программы.

Главная особенность Iron3D -- это не один монолитный engine object, а связка
небольшого Win32 bootstrap и набора DLL, которые обмениваются фабриками,
singleton-интерфейсами и C++ vtable. Совместимая реализация может изменить
физическое деление на библиотеки, но не может произвольно менять порядок
инициализации, object identity, правила владения, fallback ресурсов и порядок
событий.

```text
iron_3d.exe
  -> iron3d.dll
     -> services.dll
     -> World3D.dll
     -> Terrain.dll
     -> Ngi32.dll
     -> AniMesh.dll / ArealMap.dll / Effect.dll
     -> ai.dll / Behavior.dll / Wizard.dll
     -> Control.dll / MisLoad.dll / Net.dll / Joystick.dll
```

## Карта модулей

Во внешней архитектуре обнаружено пятнадцать DLL. Экспортов сравнительно мало:
они обычно создают объект, возвращают singleton или дают доступ к уже поднятой
подсистеме. Основная работа выполняется через C++-интерфейсы, поэтому порядок
виртуальных слотов является частью ABI, особенно для compatibility shim эпохи
MSVC6.

```text
iron_3d.exe
    |
    v
iron3d.dll  -- композиция игры, shell и главный цикл
    |
    +-- services.dll -- доступ к display, GUI, ресурсам, звуку, таймеру и сети
    +-- World3D.dll  -- объекты, очередь, время, камера и кадр
    +-- Terrain.dll  -- ландшафт, свет, атмосфера и визуальный слой мира
    +-- ai.dll / Behavior.dll / Wizard.dll
    +-- Control.dll / Effect.dll / MisLoad.dll
    +-- Net.dll / Joystick.dll
    +-- Ngi32.dll    -- ресурсы, графика, звук, математика и CPU dispatch
```

Циклы импортов между DLL ожидаемы. Terrain создаёт визуальные объекты и
обращается к World3D, а World3D получает world-interface из Terrain. Это не
значит, что обе библиотеки совместно владеют всем состоянием. Реальные границы
задаются интерфейсами, refcount, очередью объектов и порядком shutdown.

Практичная новая структура может быть внутренним набором модулей `platform`,
`resources`, `world`, `mission`, `terrain`, `render`, `animation`, `effects`,
`behavior`, `physics`, `audio` и `network`. Важно сохранить не DLL-границы, а
контракты: имена ресурсов, порядок поиска, fallback-ветки, object ID, момент
создания mirror objects, численное поведение и последовательность событий.

## Роли модулей

`iron3d.dll` создаёт shell и game objects, читает `iron_3d.ini`, поднимает
display, sound, CD-audio, network и настройки World3D, загружает миссионные и
UI-конфигурации, содержит message pump и вызывает расчёт/рендер игры.

`services.dll` работает как service locator. Через него запрашиваются display,
GUI, network manager, resource manager, sound server и timer. Этот слой отделяет
высокоуровневую игру от деталей создания устройств.

`World3D.dll` -- центральный runtime: очередь объектов, идентификаторы,
события, отложенное удаление, game time, pause, manual input, камера,
material/texture/lightmap managers, сетевые mirrors, расчёт и 3D-проход.

`Terrain.dll` отвечает не только за землю. В его область входят ландшафт,
здания, визуальный слой мира, камера, shade/state layer, primitive buffers,
сортировочные слои, источники света, тени, microtextures, атмосфера, дождь,
молнии, солнце и flares.

`Ngi32.dll` содержит низкоуровневые сервисы: DirectDraw/Direct3D-era renderer,
DirectSound, readers `NRes`/`RsLi`, память, часы, математику, пересечения,
определение CPU и таблицу быстрых процедур `g_FastProc`.

Предметные DLL закрывают отдельные области. `AniMesh.dll` загружает модели и
агентов. `ArealMap.dll` строит spatial graph и маршруты. `Behavior.dll`
реализует поведение юнитов. `ai.dll` содержит стратегический AI и миссионные
сценарии. `Wizard.dll` корректирует локальное движение. `Control.dll`
обслуживает физическую модель и столкновения. `Effect.dll` создаёт runtime-FX.
`MisLoad.dll` читает миссионные данные. `Net.dll` инкапсулирует DirectPlay.
`Joystick.dll` работает через DirectInput.

## Поток данных

Миссия не создаёт готовый кадр напрямую. Данные проходят через несколько
уровней: описание объекта, прототипы, ресурсы, runtime-object, контроллеры,
simulation state, render items и только затем платформенный renderer.

```text
mission data
  -> object identity and properties
  -> prototype registry
  -> model/material/texture/effect resources
  -> World3D object + domain controllers
  -> simulation state
  -> visible render items
  -> Ngi32 render interface
  -> DirectX-era device
```

Этот поток объясняет, почему нельзя объединять физический архив, metadata entry,
декодированный payload и готовый runtime-кэш. У каждого уровня свой срок жизни,
собственный refcount и собственные ошибки. Детали ресурсного конвейера описаны
в [Томе III](03-resources.md), а сборка мира из миссии -- в [Томе IV](04-world.md).

## Bootstrap

`iron_3d.exe` -- небольшой PE32/x86 bootstrap размером 36 864 байта. Основная
игровая логика находится в `iron3d.dll`. Исполняемый файл создаёт Win32-процесс,
подготавливает окружение, загружает библиотеку и получает восемь публичных
точек входа:

```text
createShell       deleteShell
createGame        deleteGame
createSubsystems  deleteSubsystems
getIGame          getIShell
```

Эти функции образуют внешнюю границу игры. `createShell` создаёт оболочку
интерфейса и меню, `createGame` -- объект игровой логики, `createSubsystems` --
аппаратные и runtime-сервисы. Getter-функции возвращают уже созданные объекты.

Запуск удобно читать как конечный автомат:

```text
PROCESS_CREATED
  -> LIBRARY_READY
  -> ENTRYPOINTS_READY
  -> SHELL_CREATED
  -> GAME_CREATED
  -> SUBSYSTEMS_READY
  -> MAIN_LOOP
  -> SUBSYSTEMS_CLOSED
  -> GAME_DELETED
  -> SHELL_DELETED
```

Каждый переход имеет обратное действие. Если display, sound или другой
обязательный сервис не создан, главный цикл не начинается, но уже созданные
объекты освобождаются в обратном порядке. Новая оболочка запуска должна
работать из каталога оригинальной установки, сохранять смысл относительных
путей, создавать окно до графической подсистемы и закрывать частично поднятые
сервисы без предположения, что init дошёл до конца.

Bootstrap обеих полных частей побайтно одинаков, хотя файл второй части может
иметь другое имя:

```text
size       36 864
entry RVA  0x147E
SHA-256    f476af85c034a4b4f34f49d0806e4dff397b5da0ee26d382a7674231144979f7
```

Следовательно, различия полных частей начинаются после передачи управления DLL
и игровым данным. Адреса executable демоверсии относятся к другой binary
profile и не должны переноситься на полные версии без проверки hash.

## Инициализация подсистем

Iron3D разделяет создание высокоуровневых объектов и создание подсистем.
`createShell` конструирует оболочку пользовательского интерфейса, `createGame`
создаёт объект игры, а `createSubsystems` связывает их с display, sound,
network и World3D.

Высокоуровневая последовательность выглядит так:

```text
прочитать iron_3d.ini
  -> получить display service
  -> создать окно и графическое устройство
  -> проверить доступность 3D-драйвера
  -> выбрать CURRENT_D3DCARD
  -> получить sound service и настроить громкость
  -> создать network instance и передать application GUID
  -> создать World3D game settings
```

Ошибка отсутствующего 3D-устройства обрабатывается отдельно от ошибок ресурсов:
это разные стадии запуска. Конфигурация влияет не только на разрешение. В
runtime попадают графическая карта, громкость эффектов, CD-audio, режим
CD-sound, сетевое приложение и World3D settings. Application GUID сетевой
подсистемы:

```text
{3C1D1F01-A870-11D1-8400-000021B14415}
```

Один и тот же GUID передаётся сетевому объекту и service layer. Если он
разойдётся, экземпляры игры станут логически разными приложениями, даже при
исправном транспорте.

## `stdInitGame`

После платформенных сервисов World3D создаёт внутренний runtime:

1. Создаёт глобальную очередь объектов.
2. Сохраняет window handle и режим игры.
3. При нужном режиме ограничивает курсор областью окна.
4. Получает или создаёт 3D sound object.
5. Загружает реестр адресов компонентов из `Comp.ini`.
6. Получает или создаёт 3D renderer.
7. Читает профиль возможностей renderer.
8. Загружает component type 6.
9. Для multiplayer создаёт NetWatcher.
10. Получает world-interface из Terrain.
11. Устанавливает исходные параметры света и тумана.

Порядок важен. World objects не должны появляться до queue, ресурсы рендера --
до renderer, сетевые mirror objects -- до NetWatcher. В новой реализации у
каждого этапа должен быть явный признак успешного создания, чтобы shutdown мог
безопасно разобрать неполный init.

## Завершение

Shutdown идёт в обратном направлении: прекращаются игровые расчёты и сетевые
наблюдатели, разбираются отложенные операции, освобождаются world objects и
менеджеры, затем renderer и sound, затем game settings и platform services.
Ограничение курсора снимается, глобальные ссылки очищаются.

Полезный протокол завершения:

1. Запретить новые события и новые объекты.
2. Дождаться выхода из calculation/render traversal.
3. Разобрать очередь deferred operations.
4. Отсоединить объекты от очереди, контроллеров и менеджеров.
5. Освободить managers и singletons после их consumers.
6. Закрыть устройства и платформенные сервисы.

Такой порядок защищает от dangling-ссылок между World3D, Terrain, renderer,
sound и сетевым слоем.

## Главный цикл

Главный цикл -- не одна функция `update_and_render`, а расписание, связывающее
Win32 messages, input, игровые события, таймеры, сеть и renderer. Системная
очередь сообщает об активации окна, вводе, изменении состояния процесса и
выходе. Очередь World3D рассчитывает игровые объекты. У этих очередей разные
правила времени и владения, поэтому их нельзя смешивать в один контейнер.

Подтверждённые точки вызова в одном из профилей:

```text
stdCalculateGame       RVA 0x5FA94, 0x604C1, 0x6086B
ClearManualEventsList  RVA 0x6052F
stdRenderGame          RVA 0x60B2F
UpdateManualEventsList в обработчике сообщений около RVA 0xA3759
```

Смысловой skeleton:

```c
while (running) {
    stdCalculateGame();
    clear_keyboard_snapshot();
    update_shell_and_mode();
    ClearManualEventsList();
    process_window_messages();
    update_timers_ui_gameplay_network();
    if (mode_requires_extra_step) stdCalculateGame();
    if (render_enabled) {
        stdSetCurrentCamera(camera);
        stdRenderGame(camera);
    } else {
        sleep_briefly();
    }
    update_post_render_state();
}
```

Ввод из window messages накапливается между расчётными шагами. Если читать
клавиатуру только внутри рендера, события будут теряться при пропущенных кадрах
или отключённом выводе.

## `stdCalculateGame`

Calculation pass сначала очищает или подготавливает список manual events,
увеличивает внутренний depth/counter и опрашивает input device. Если устройство
временно потеряно, выполняется повторное получение доступа и чтение повторяется.
Затем при незамороженной игре выставляется признак `in_calculation` и вызывается
основной traversal очереди объектов.

```text
prepare input/events
  -> enter calculation
  -> dispatch queue events
  -> objects update behavior and transforms
  -> leave calculation
  -> apply deferred operations
  -> occasional cache maintenance
```

После traversal разбирается deferred-delete list. Объект может запросить
собственное удаление во время события, но память освобождается только после
завершения обхода. Периодически также очищаются давно неиспользуемые ресурсы и
объекты по порогам часов порядка 20 и 60 секунд.

Совместимый runtime должен иметь явный traversal depth или флаг
`in_calculation`. Нельзя полагаться на то, что контейнер выдержит удаление
текущего элемента из обработчика события.

## Жизненный цикл кадра

Рендер читает состояние, подготовленное расчётом. Кадр начинается до renderer-а:
message pump уже накопил ввод, World3D уже обновил объекты, отложенные операции
и анимации, после чего выбирается камера и обновляется listener звука.

```text
system messages and input
  -> simulation calculation
  -> deferred object operations
  -> animation and transforms
  -> camera and sound listener
  -> visibility and render queues
  -> materials and draw passes
  -> renderer completion
  -> end-of-render callbacks and UI
```

В `World3D::stdRenderGame` виден крупный каркас: установка camera/viewport,
renderer frame boundaries, traversal мира, завершение world/shade path,
renderer completion, снятие `in_render`, восстановление viewport и рассылка
end-of-render callbacks. Эти callbacks позволяют объектам безопасно обновить
временные ресурсы после того, как draw-команды больше их не используют.

Один calculation step не обязан соответствовать одному изображению. Главный
цикл допускает дополнительный вызов `stdCalculateGame` и режим, в котором
расчёт продолжается без вывода кадра. Поэтому нужно хранить отдельно:

1. монотонные платформенные часы;
2. игровое время с pause и масштабированием;
3. длительность текущего calculation step;
4. локальное время анимации и FX;
5. реальные часы обслуживания кэшей.

Игровую логику нельзя выводить из render delta: изменение частоты кадров тогда
изменит движение, камеру и сценарные таймеры. Подробности render item и рисков
кадровой совместимости вынесены в справочник [Render frame](../reference/render-frame.md).

## World3D

World3D связывает игровые объекты, события, время, ввод, камеру, сетевые
отражения и визуальное представление. Он не содержит всю предметную логику:
движение делегируется Behavior/Wizard, физика -- Control, мир -- Terrain. Его
задача -- общая идентичность, порядок вызовов и безопасный жизненный цикл.

`CreateQueue` создаёт singleton-объект размером 20 байт, а `GetQueue`
возвращает его. Очередь служит центральным маршрутизатором событий и операций
над объектами.

Публичный слой предоставляет отдельные функции для локальных и сетевых
объектов:

```text
CreateObject
AddObjectToGame
AddNewObjectToGame
CreateMirrorObject
AddMirrorObjectToGame
AddNewMirrorToGame
```

Разделение "создать" и "добавить в игру" означает два этапа: сначала выделить и
настроить instance, затем зарегистрировать его в общей системе. Это позволяет
loader-у заполнить свойства до появления объекта в расчётной очереди.

## Идентичность объектов

Object ID кодирует не только порядковый номер. Проверки диапазонов показывают
разбиение на номер игрока, класс и индекс. Mirror object представляет объект,
владельцем которого является другой участник. Локальный runtime хранит его
видимое состояние, но источник авторитетных изменений находится удалённо.

```c
struct ObjectId {
    uint32_t raw;
    uint16_t owner_player;
    uint16_t class_and_index;
};
```

Точное битовое разбиение нужно брать из сетевых функций. На уровне API уже
сейчас полезно разделить логические свойства: `is_local`, `is_mirror`, `owner`,
`class` и `index`.

Минимальный runtime-object должен хранить identifier, type, owner, transform,
active state, ordered property bag, ссылки на controllers, участие в расчёте и
рендере, сетевой статус и флаг отложенного удаления. Специализированные DLL
могут быть представлены компонентами, но порядок их вызовов задаёт World3D.

## Отложенное удаление

`DeleteGameObject` проверяет, идёт ли calculation pass. Если обход активен и
удаление не принудительное, объект помещается в deferred list. `KillGameObject`
отправляет запрос через очередь, а не освобождает память напрямую.

```c
void request_delete(Object* o) {
    if (world.in_calculation) {
        world.deferred_delete.push_back(o);
        o->pending_delete = true;
    } else {
        world.detach_and_release(o);
    }
}
```

Это защищает итераторы, связи и текущий стек вызовов. Любая новая подсистема,
способная удалить объект из обработчика события, обязана пользоваться тем же
механизмом.

Регистрация в очереди и владение памятью -- разные понятия. Удаление из мира не
всегда означает немедленное освобождение instance: часть объектов и managers
использует intrusive reference count, а renderer, sound и resource managers
могут возвращать уже существующий singleton с увеличенным счётчиком. Поэтому
global manager закрывается после всех объектов, которые на него ссылаются.

## Детерминизм

Даже при одинаковых формулах результат зависит от порядка. Стабильный runtime
сохраняет последовательность queue traversal, момент формирования input
snapshot, порядок сетевых сообщений, обработку deferred operations и порядок
обращений к RNG. Оптимизация и многопоточность допустимы только при
детерминированном объединении результатов.

Для переносимой реализации полезно разделить scheduler phases и immutable
render snapshot. Это архитектурная рекомендация для новой реализации, а не
утверждение о точном layout исходных C++ classes.

## Стабильность между сборками

Внешняя архитектура полных Частей 1 и 2 сохраняет те же пятнадцать DLL, 313
exports, имена, ordinals и import sets. Побайтно идентичны:

```text
ai.dll, Behavior.dll, Joystick.dll, MisLoad.dll, Net.dll,
Ngi32.dll, Terrain.dll, Wizard.dll, World3D.dll
```

Пересобраны:

```text
AniMesh.dll, ArealMap.dll, Control.dll, Effect.dll,
iron3d.dll, services.dll
```

Это разделяет переносимость выводов. World3D lifecycle, Terrain, NRes/RsLi
readers, mission loader, AI/Behavior/Wizard, DirectPlay wrapper и joystick
adapter подтверждаются одной машинной реализацией. Model/agent runtime,
collision, effects, shell/composition и service layer требуют отдельного
сравнения поведения Частей 1 и 2.

Для `World3D.dll` Частей 1 и 2 применим общий hash:

```text
World3D.dll SHA-256
17e4a3089b2583a8cf2356c9db0390b1aba138356a09130d79b4e7e4791da61e
```

RVA внутри `iron3d.dll` нельзя считать общими без проверки конкретного файла:
эта DLL пересобрана между частями, а демоверсия имеет отдельный binary profile.
Смысловая последовательность цикла переносится как контракт scheduler-а, но
адреса остаются build-specific.