- MainActivity фиксирует ландшафт (SDL перебивал манифест FULL_USER) и передаёт путь к ассетам через getExternalFilesDir; - короткий тап по JUMP не теряется между кадрами (jump_pending); - HUD сдвинут внутрь от статус-бара и нижней панели; - логгер на Android собирается без неиспользуемой ветки.
14 KiB
14 KiB
Архитектура Source RS
1. Workspace
crates/
tier — tracing-логи, конвары, CommandLine, конфиг, время, пулы, interning
math — Vector/Angle (Source-семантика углов), Matrix, Quaternion, AABB, plane
fs — поисковые пути и порядок монтирования: dir, VPK v1/v2, GMA,
BSP pakfile, mount.cfg, mmap, кэш индексов (rkyv)
formats — kv (KeyValues), bsp, vtf, vmt, mdl (+vvd/vtx/anim), pcf, phy, snd, demo
net — netchan (UDP, фрагментация, bz2), dt (send/recv tables), stringtable,
usermessages, GMod-расширения, мост к Steam
engine — мир, entity-стор (архетипы/пулы), интерполяция, prediction, usercmd,
interest-система (классификация, важность, бюджеты)
render — wgpu: device/swapchain, HDR-таргет, material system (compat-семантика +
современный shading), шейдеры, инстансинг, RT-менеджер, post, pipeline cache
studio — StudioRender: mdl-инстансы, кости, blend-граф, IK, procedural bones, flex,
LOD, аттачменты
physics — Jolt (C-API + свой безопасный слой), hibernation, prediction-мир, рагдоллы
audio — kira (микс) + symphonia (декод), пространственность, звуковые скрипты
ui — нативный UI: меню, консоль, шрифты, HUD-рамка, загрузочные экраны
lua — mlua (vendored LuaJIT), sandbox, рельсы API, мосты к engine/render/studio
steam — фичи steamworks-rs (тикет, UGC/workshop), отдельный steam-поток
client — бинарь `gmod-rs` и библиотека: сборка всего, главный цикл,
конфиг, CLI; на Android — cdylib с `SDL_main` (см. `ANDROID.md`)
Правило зависимостей
Строго «вниз по списку»:
tier, math → fs, formats → net → engine → render, studio, physics, audio, ui → lua → client
luaиclientвправе зависеть от всего (lua — движковый API, client — композиция).- Обратные зависимости запрещены;
renderможет зависеть отstudio(модели),engine— отnet(типы снапшотов), но не наоборот.
2. Технологический стек и почему
| Задача | Выбор | Почему |
|---|---|---|
| GPU | wgpu (Vulkan на Linux) | Современные фичи, кроссплатформенность, pipeline cache, compute |
| Окно/ввод | SDL3 (при проблемах — winit) | Геймпады, IME, зрелость, кроссплатформенность |
| Физика | Jolt через C-API | Де-факто замена VPhysics в современных Source-подобных; job-friendly |
| Lua | mlua + vendored LuaJIT | Семантика 5.1 как в GMod + производительность |
| Звук | kira + symphonia | Гибкий микс + декод mp3/ogg/wav |
| Steam | steamworks-rs | Тикет, UGC/workshop, браузер позже |
| Форматы | Свои парсеры + bzip2, LZMA (raw LZMA1) |
Полный контроль, bounded, fuzzable |
| Кэши | rkyv/bincode, mmap (memmap2) |
Быстрый старт, индекс VPK, обработанные ассеты |
| Логи | tracing |
Каналы, фильтры, интеграция с dev-тулингом |
| Ошибки | thiserror (крейты) + anyhow (границы приложения) |
Явные типы, удобный верхний уровень |
3. Подсистемы
3.1 FileSystem (fs)
Порядок монтирования (уточняется в M1 по поведению GMod):
platform → sourceengine (hl2-база) → garrysmod_*.vpk → garrysmod/addons/*
→ workshop (по списку сервера, через Steam) → download-кэш (lua/контент с сервера)
→ BSP pakfile (мапа) → write-пути (наш конфиг/cache)
mount.cfgпользователя учитывается (~/gmod_server/server/garrysmod/cfg/mount.cfg).- Все открытия — через единый интерфейс с нормализованными путями (защита от traversal).
- Индекс VPK кэшируется на диск (content-hash + версия парсера).
3.2 Сеть (net)
Конвейер:
UDP → netchan (сборка фрагментов, bz2) → потоки сообщений → signon/delta-декод
→ очередь «снапшот готов» → применение на главном потоке (тик интерполяции)
- Никаких блокировок в горячих путях: net-поток кладёт пакеты в lock-free очередь, декод — в worker-пулах, применение — двойная буферизация.
- Все ограничения входных данных — явные (размеры строк/массивов, глубина, количество энтити).
- Dump/replay:
netумеет писать/читать сырые потоки с метаданными (см.NETWORK.md).
3.3 Entity-система (engine)
- Хранение — архетипами/пулами (SoA-ish), стабильные ID, без аллокаций на сущность в кадре.
- Клиентские энтити — прокси для рендера/физики; Lua-фасад создаётся лениво, только если аддоны реально трогают сущность.
- Интерполяция: буфер из 2–3 снапшотов, рендер с задержкой (как в Source MP).
- Prediction: локальный игрок (movement по SDK), usercmd, реконсиляция.
3.4 Interest & Visibility (engine + render)
«Принцип сандбокс-масштаба» (детали — PROBLEMS.md §P2):
- Состояния:
Static → Sleeping → Dynamic → Impostor → Skipped(обратимо). - Важность: экранный размер, дистанция, движение, свежесть сети, взаимодействие.
- Бюджеты: анимации (60/20/0 Гц), скиннинг, тени, партиклы, обновление костей.
- Инстансинг повторяющихся моделей; merge спящих пропов в статические батчи.
- Куллинг: PVS + frustum + octree, многопоточно; fallback без PVS.
- Правило совместимости: куллинг не влияет на существование сущностей и на Lua-draw (PAC3/Starfall/HUD видят всё; у них свои бюджеты).
3.5 Рендер (render)
Два слоя:
- Compat-семантика — точный render state и поведение материалов Source:
render modes (
$additive,$translucent,$alphatest), RT, lightwarps, cubemaps, eyes, skinning, decals, halos. Контракт: аддоны не должны отличать нашу картинку от GMod. - Современный shading — HDR-конвейер (RGBA16F), линейное освещение, tonemap, bloom, SSAO, SMAA (TAA опцией), SSR воды, динамические тени моделей (опцией), анизотропия.
Прочее: pipeline cache на диске; параллельная запись команд; shadow/RT-бюджеты; texture streaming и VRAM-бюджет (с M9).
Шейдеры по приоритету: LightmappedGeneric → VertexLitGeneric → UnlitGeneric → вода → спрайты → декали/эффекты → прочее. Аддонские DXBC — вне первой фазы.
3.6 StudioRender (studio)
- Загрузка: MDL/VVD/VTX/ANIM,
$keyvalues, bodygroups, аттачменты. - Анимация: blend-граф (секвенции/жесты/activity layers), IK, procedural bones
(
$bonefollowers), flex/eyeballs. - LOD: по важности; GPU-скиннинг в storage buffers; фриз костей для дальних.
- Ragdoll-позы + клиентская физика (Jolt).
3.7 Потоки и конкурентность
- Main — ввод, применение снапшотов, Lua (как в GMod), запись кадра.
- Pool — куллинг, скиннинг-подготовка, декод ассетов, копирование данных на GPU.
- Net — сокет и netchan; декод в отдельных worker'ах.
- Physics — Jolt jobs (многоядерно), hibernation дальних островов.
- Steam — отдельный поток: Steam API не потокобезопасен, все вызовы там; наружу — каналы.
- Lua — главный поток (как в GMod); тяжёлые вычисления — только через явные API; watchdog следит за зависаниями.
3.8 Ассет-пайплайн и кэши
Каталог кэша (по умолчанию ~/.cache/source-rs, настраивается):
| Кэш | Содержимое | Инвалидция |
|---|---|---|
vpk-index/ |
Индексы VPK/GMA | content-hash + версия парсера |
textures/ |
Обработанные текстурные блобы+mips | хэш исходника + версия |
meshes/ |
Готовые vertex/index-буферы, импосторы | хэш + версия |
pipelines/ |
wgpu pipeline cache | версия драйвера/шейдеров |
lua-bc/ |
Байткод LuaJIT (опц.) | хэш + версия LuaJIT |
downloads/ |
Файлы, скачанные с серверов | хэш (вечный кэш) |
Цель: второй и последующие запуски — секунды; серверные файлы не качаются повторно.
3.9 Конфигурация
~/.config/source-rs/config.toml(или--config): корни ассетов, каталог кэша, серверы, фичи.- Авто-детект ассетов: (1) явный путь; (2)
~/gmod_server/server; (3) Steam common. - Внутриигровые настройки — конвары (как в Source), сохраняются в
cfg/.
3.10 Android
- Тот же клиент собирается как
cdylibи грузится штатнойSDLActivityиз SDL3; экспорт —SDL_main. Сборка APK —android/build.sh(aapt2 + d8, без Gradle), debug-подпись. - Целевые ABI:
arm64-v8a(телефон) иx86_64(эмулятор).minSdk 31,targetSdk 35, фиксированный ландшафт. target.'cfg(target_os = "android")': безsteamworks, Lua 5.4 вместо LuaJIT; логирование в logcat.- Ассеты — во внешнем каталоге приложения; касания (
FingerDown/Motion/Up) переведены в те жеwish_forward/right, прыжок и присед, что клавиатура. - Детали и ограничения —
docs/ANDROID.md, команды установки —android/README.md.
4. Конвенции кода
unwrap/expect/panicзапрещены в путях обработки внешних данных (сеть/диск/Lua). Для внутренних инвариантов —debug_assertи явные ошибки.- FFI-границы (Jolt, Steam, Lua) оборачиваются в безопасные слои; все входы проверяются.
catch_unwind— на границах Lua/FFI/worker; паника одного аддона/задачи не убивает клиент.- Без
unsafeвне FFI-обёрток; FFI-крейты изолированы. - Горячие пути — без аллокаций; interning строк; пулы.
- Ошибки — типизированные (
thiserror) в крейтах,anyhowна верхнем уровне. - Тесты — рядом с кодом; fuzz-таргеты для всех парсеров; бенчмарки для hot paths.
5. Бюджеты производительности (ориентиры для M9)
| Стадия | Бюджет (мс/кадр, RTX 3060, стресс-сцена) |
|---|---|
| Сеть/декод (не main) | ≤ 2 |
| Применение снапшотов | ≤ 1 |
| Interest/куллинг | ≤ 1 |
| Анимации (подготовка) | ≤ 2 |
| Запись рендер-команд | ≤ 3 |
| Main total | ≤ 8 (цель 60+ FPS со запасом) |
6. Схема кадра (упрощённо)
Input → Usercmd → Prediction(local)
Net queue → Snapshot apply → Entities → Interp states
Interest/Visibility → cull → instancing/static batches → anim LOD → render graph → present
Lua: Frame → HUD/Paint → Render hooks (в порядке как в GMod; sandbox)
Physics (client-side) → интерполяция/рагдоллы