From 78fc5f1debf1395d5df0bab7cc0dde54351205cb Mon Sep 17 00:00:00 2001 From: Valentin Popov Date: Mon, 22 Jun 2026 01:58:51 +0400 Subject: docs: rewrite MkDocs documentation --- docs/tomes/02-architecture.md | 472 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 472 insertions(+) create mode 100644 docs/tomes/02-architecture.md (limited to 'docs/tomes/02-architecture.md') diff --git a/docs/tomes/02-architecture.md b/docs/tomes/02-architecture.md new file mode 100644 index 0000000..b44bd51 --- /dev/null +++ b/docs/tomes/02-architecture.md @@ -0,0 +1,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. -- cgit v1.2.3