Skip to content

Repository files navigation

RustCore — Rust-ускоритель и визуальный тюнер для Garry's Mod (Metrostroi)

Нативный модуль на Rust, который выступает диспетчером и вычислительным ядром для аддонов GMod — с уклоном в оптимизацию и визуальный тюнинг Metrostroi Subway Simulator, но не завязан на него жёстко.

Что умеет

  • Диспетчер хуков с приоритетами и встроенным профилированием — любой Lua-аддон подписывается одной строкой, порядок и медленные обработчики видно сразу.
  • Rust-вычисления из Lua — синхронно (CallSync, CallSyncParallel) или асинхронно в отдельных потоках (StartTask), с автоматическим распределением по всем ядрам процессора.
  • Плагины подгружаются на лету — отдельно скомпилированные .dll/.so без пересборки самого RustCore и без рестарта сервера.
  • Адаптивная система освещения — экранная цветокоррекция и bloom, реагирующие на освещённость сцены в реальном времени: гамма, усиление, контраст, насыщенность, цветовой баланс — всё крутится ползунками в игровой панели, без единой строчки консольных команд.
  • Встроенный профилировщик — хуки и таймеры любых аддонов (не только RustCore), с автосохранением отчётов.
  • Реальный, проверенный фикс одного из самых тяжёлых мест Metrostroi (пересчёт электропитания сети) — с мгновенным откатом и самодиагностикой на случай, если у конкретной сборки что-то пойдёт не так.
  • Панель настроек (rustcore_menu) — свет, список плагинов, управление профайлером, без консоли.

⚠️ Важно про лицензию Metrostroi

RustCore не содержит и не модифицирует ни строчки защищённого лицензией кода Metrostroi (lua/metrostroi/systems/*.lua — электрика, пневматика, релейная логика). Вся интеграция идёт только через публичные точки расширения GMod и Metrostroi: hook.GetTable(), глобальные Lua- таблицы (Metrostroi.SpawnedTrains и т.п.), scripted_ents.Get(). Это осознанный принцип проекта, не формальность.

⚠️ Важно про доставку бинарников игрокам

В отличие от Lua, которую GMod раздаёт клиентам через Workshop/FastDL автоматически, .dll/.so-файлы так не доставляются — это ограничение движка. Каждый игрок должен вручную положить клиентский бинарник (gmcl_rustcore_*.dll) себе в garrysmod/lua/bin/. Подробнее и с пошаговой инструкцией — в руководстве Steam (STEAM_GUIDE.md).

Установка (кратко)

  1. Собери (см. «Сборка» ниже) или возьми готовые бинарники из релиза.
  2. .dll/.sogarrysmod/lua/bin/ (и на сервере, и у каждого клиента — см. предупреждение выше).
  3. Содержимое lua_addon/lua/garrysmod/lua/ (или в отдельный аддон garrysmod/addons/rustcore/lua/).
  4. Плагины (example_plugin.dll, light_plugin.dll) → garrysmod/rustcore_plugins/.
  5. Проверка: lua_run print(RustCore) должно вернуть таблицу, не nil.

Полная версия с разбором частых ошибок — в STEAM_GUIDE.md.

Быстрый старт

rustcore_menu                    -- панель настроек (свет, плагины, профайлер)
rustcore_profiler_enabled 1      -- начать сбор статистики по хукам/таймерам
rustcore_profiler_report         -- топ-20 по суммарному времени
-- Подписать свой аддон на событие (приоритет: больше = раньше вызов)
RustCore.RegisterHook("Think", "my_addon", 0, function()
    -- ...
end)

Документация для разработчиков

Требования для сборки

  • Rust nightly (крейт gmod этого требует):
    rustup toolchain install nightly
    rustup override set nightly   # внутри папки проекта — rust-toolchain.toml это уже форсирует
    
  • Кросс-компиляция под нужные таргеты:
    rustup target add i686-pc-windows-msvc
    rustup target add x86_64-pc-windows-msvc
    rustup target add i686-unknown-linux-gnu
    rustup target add x86_64-unknown-linux-gnu
    
    Для сборки Windows-таргетов с Linux/macOS понадобится cargo-xwin или cross — нативный MSVC-линкер кросс-компиляцией "из коробки" не собирается. Проще всего собирать _win* таргеты прямо на Windows, а _linux* — на Linux (или через cross в Docker).
  • example_plugin/ и light_plugin/ — отдельные, самостоятельные Cargo-проекты, собираются стабильным Rust (без зависимости на gmod, cargo build --release без +nightly).

Именование файлов (важно!)

GMod ищет модули по строгому шаблону <gmXX>_<name>_<platform>.<ext>:

Сборка Платформа Итоговое имя файла Rust target
Сервер Windows x64 gmsv_rustcore_win64.dll x86_64-pc-windows-msvc
Сервер Linux x64 gmsv_rustcore_linux64.dll x86_64-unknown-linux-gnu *
Клиент Windows x86 gmcl_rustcore_win32.dll i686-pc-windows-msvc
Клиент Windows x64 gmcl_rustcore_win64.dll x86_64-pc-windows-msvc
Клиент Linux x64 gmcl_rustcore_linux64.dll x86_64-unknown-linux-gnu *

* GMod на Linux исторически ждёт файл с расширением .dll (не .so) даже для линуксовой сборки — просто переименуй собранный .so в .dll.

GMod сейчас постепенно переезжает на 64-битную ветку (x86-64 branch) — если таргетишь именно её, и сервер, и клиент 64-битные на обеих ОС. Легаси-ветка (по умолчанию у многих серверов) — клиент 32-бит на Windows. Собери сразу несколько вариантов, если не уверен, на какой ветке будут игроки.

Сборка

# из папки rustcore/
cargo +nightly build --release --target x86_64-unknown-linux-gnu
cargo +nightly build --release --target x86_64-pc-windows-msvc

# плагины — отдельно, стабильный Rust
cd light_plugin && cargo build --release && cd ..
cd example_plugin && cargo build --release && cd ..

Собранный файл лежит в:

  • Windows: target/<TARGET>/release/rustcore.dll
  • Linux: target/<TARGET>/release/librustcore.so

Переименуй согласно таблице выше (убери префикс lib, поставь gmsv_/gmcl_, поставь .dll) и положи в garrysmod/lua/bin/.

Установка Lua-части

Скопируй содержимое lua_addon/lua/ в garrysmod/lua/ (или сразу в аддон garrysmod/addons/rustcore/lua/):

lua/autorun/rustcore_init.lua                    -- обязательно
lua/autorun/server/rustcore_profiler.lua         -- профайлер (сервер)
lua/autorun/server/rustcore_electric_optimize.lua-- фикс электрики (сервер, требует Metrostroi)
lua/autorun/client/rustcore_light_bloom.lua      -- адаптивный свет (клиент)
lua/autorun/client/rustcore_menu.lua             -- панель настроек (клиент)
lua/example_addon/example.lua                    -- пример, можно удалить

Проверка

При старте сервера/клиента в консоли должно появиться:

[RustCore] модуль загружен
[RustCore] диспетчер запущен

API

Функция Назначение
RustCore.RegisterHook(event, owner, priority, fn) Подписать fn на событие. Выше priority — раньше вызов.
RustCore.RemoveHook(event, owner) Снять хук(и) владельца с конкретного события.
RustCore.RemoveAllHooks(owner) Снять вообще все хуки владельца (на выгрузке аддона).
RustCore.Dispatch(event, ...) Точка входа — вызывается из hook.Add на стороне GMod.
RustCore.HookCount(event) Сколько обработчиков подписано.
RustCore.SetSlowHookThreshold(ms) С какого времени выполнения хук считается "медленным" и логируется (по умолчанию 2 мс).
RustCore.CallSync(kind, payload) Синхронный вызов задачи/плагина — без потоков, результат сразу же (ok, data).
RustCore.CallSyncParallel(items) Несколько CallSync ОДНОВРЕМЕННО (пул rayon). items = { {kind=.., payload=..}, ... }{ {ok=.., data=..}, ... }.
RustCore.StartTask(kind, payload, callback) Асинхронная задача в отдельном потоке. callback(ok, data).
RustCore.PollTasks() Забрать готовые результаты асинхронных задач — дёргать регулярно (на Think).
RustCore.LoadPlugins(dir) Просканировать ОС-папку и подгрузить все .dll/.so-плагины из неё.
RustCore.ListPlugins() Список уже загруженных плагинов (строка через запятую).

Как добавить свой тип асинхронной задачи

Задачи регистрируются на стороне Rust, не Lua — задача выполняется в отдельном потоке и должна быть чистым нативным кодом (без обращения к lua_State). Добавляется в gmod13_open (src/lib.rs):

tasks::register_task_kind("my_kind", |payload| {
    Ok(format!("результат для {}", payload))
});

Дальше из Lua: RustCore.StartTask("my_kind", "данные", function(ok, data) ... end).

Профилирование медленных хуков (встроенное в Dispatch)

RustCore.Dispatch сам замеряет время каждого вызванного обработчика и, если оно превышает порог (RustCore.SetSlowHookThreshold, по умолчанию 2 мс), печатает в консоль:

[RustCore] МЕДЛЕННЫЙ ХУК: 'Think' владельца 'example_addon' занял 4.13 мс

Профилировщик хуков и таймеров (rustcore_profiler.lua)

Отдельный инструмент, который оборачивает таймингом вообще все зарегистрированные hook.Add (свои, Metrostroi, чужие) через публичный hook.GetTable(), и timer.Create (с оговоркой: таймеры, созданные до загрузки этого файла, не видны — публичного способа их перечислить у GMod нет). Ни один файл Metrostroi не читается и не меняется.

rustcore_profiler_enabled 1              -- включить сбор статистики
rustcore_profiler_report                 -- топ-20 хуков/таймеров по суммарному времени
rustcore_profiler_threshold_ms 2         -- дополнительно спамить в лог всё, что дольше 2 мс
rustcore_profiler_autosave_interval 300  -- как часто сохранять отчёт в файл, сек (0 = выкл)

Отчёт автоматически копится в data/rustcore_profiler_report.txt.

Динамическая загрузка плагинов

local count, names = RustCore.LoadPlugins("garrysmod/rustcore_plugins")
RustCore.StartTask("example_plugin", "какие-то данные", function(ok, data)
    print(ok, data)
end)

LoadPlugins(dir) сканирует ОС-папку и через libloading грузит каждый .dll/.so с двумя обязательными экспортами: rustcore_run и rustcore_free_buffer (контракт — см. example_plugin/src/lib.rs). Имя файла (без расширения) становится kind задачи. Выполнение — в пуле потоков rayon (авто-распределение по ядрам), результат всегда доставляется строго в главный/Lua поток.

CallSync — синхронный вызов, работает на сервере и на клиенте одинаково

StartTask/PollTasks асинхронны — результат приходит через кадр-другой. RustCore.CallSync(kind, payload) — вызывает обработчик/плагин прямо в текущем потоке, без очереди, результат мгновенно:

local ok, data = RustCore.CallSync("example_plugin", "какие-то данные")

Осторожно: без потоков — любая медленная функция здесь напрямую тормозит тик сервера/кадр клиента. Годится только быстрая чистая математика, не сеть/диск. Для тяжёлой работы — StartTask.

RustCore.CallSyncParallel(items) — то же самое, но сразу несколько задач параллельно (пул rayon), общее время = времени самой медленной задачи.

light_plugin — адаптивное освещение

Плагин считает параметры для штатных экранных пост-эффектов GMod: DrawBloom (свечение) и DrawColorModify (гамма/яркость/контраст/ насыщенность/цветовой баланс). Не создаёт, не гасит и не патчит отдельные источники света — работает поверх всего, что уже нарисовано на экране, не завязан на конкретные сущности/классы.

Логика: адаптивная база по темноте вокруг игрока (render.GetLightColor, сэмплится строго внутри рендер-хука — вне контекста рендера может вернуть мусор) + ручные множители/сдвиги сверху, всё сглаживается во времени внутри плагина (Mutex-состояние между вызовами) и жёстко клампится на выходе.

Ручные конвары (или через панель rustcore_menu):

Конвар Диапазон Что делает
rustcore_bloom_enabled 0/1 Включить/выключить оба эффекта
rustcore_bloom_interval 0.01-0.5 Как часто пересчитывать через Rust, сек
rustcore_light_gamma -0.3..0.3 Гамма/яркость
rustcore_light_exposure 0.3..3 Усиление свечения (bloom)
rustcore_light_contrast 0.3..2.5 Контраст
rustcore_light_saturation 0..2.5 Насыщенность (0 = ч/б)
rustcore_light_red_cyan / _green_magenta / _blue_yellow -0.2..0.2 Цветовой баланс — противоположные концы = комплементарные цвета, середина = нейтрально
rustcore_light_red_255 / _green_255 / _blue_255 0..255 Отдельная ручка на канал, 128 = нейтрально

Собирается отдельно (light_plugin/, стабильный Rust), .dll нужен на клиенте у каждого игрока (клиентская фича).

Metrostroi_ElectricConsumptionThink — фикс найденного боттлнека

rustcore_electric_optimize.lua заменяет самый тяжёлый хук по данным профайлера — оригинал у Metrostroi каждый тик делает ents.FindByClass() в цикле по всем классам вагонов (полный обход сущностей карты много раз за тик). Наша версия использует готовый кэш Metrostroi.SpawnedTrains + свой кэш bogey. Математика расчёта тока/напряжения не тронута — 1:1 то же самое, поменялся только источник данных.

Риск: если кэш Metrostroi.SpawnedTrains у конкретной сборки/форка не покрывает все типы вагонов — суммарный ток считается заниженным, значит напряжение сети завышено, тяга/торможение могут реагировать резче, чем должны. Защита:

rustcore_electric_optimize_enabled 0   -- мгновенный откат на оригинальную логику Metrostroi

Плюс каждые 15 сек — диагностика в консоль, сверяющая размер кэша с реальным сканом мира.

Панель настроек

rustcore_menu (или Q → Утилиты → RustCore): вкладки Свет (bloom, гамма, контраст, насыщенность, цветовой баланс, каналы), Плагины (список загруженных, обновить), Профайлер (вкл/выкл, вывод отчёта).

Лицензия

Код самого RustCore (Rust и Lua, всё в этом репозитории) — [укажи свою лицензию, например MIT]. Это не даёт прав на код или ассеты Metrostroi — для него действует отдельная лицензия автора (license.txt в составе самого Metrostroi), RustCore её не затрагивает и не переопределяет.

Что дальше стоит доделать

  • Реальные обработчики задач под конкретные сценарии (сейчас только пример "echo").
  • Профилирование Metrostroi.GetPositionOnTrack/UpdateTrainPositions по той же схеме, что и электрика.
  • Клиентский профайлер (FPS), по аналогии с серверным.
  • Пресеты для панели света (сохранение/загрузка именованных наборов).

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages