Нативный модуль на Rust, который выступает диспетчером и вычислительным ядром для аддонов GMod — с уклоном в оптимизацию и визуальный тюнинг Metrostroi Subway Simulator, но не завязан на него жёстко.
- Диспетчер хуков с приоритетами и встроенным профилированием — любой Lua-аддон подписывается одной строкой, порядок и медленные обработчики видно сразу.
- Rust-вычисления из Lua — синхронно (
CallSync,CallSyncParallel) или асинхронно в отдельных потоках (StartTask), с автоматическим распределением по всем ядрам процессора. - Плагины подгружаются на лету — отдельно скомпилированные
.dll/.soбез пересборки самого RustCore и без рестарта сервера. - Адаптивная система освещения — экранная цветокоррекция и bloom, реагирующие на освещённость сцены в реальном времени: гамма, усиление, контраст, насыщенность, цветовой баланс — всё крутится ползунками в игровой панели, без единой строчки консольных команд.
- Встроенный профилировщик — хуки и таймеры любых аддонов (не только RustCore), с автосохранением отчётов.
- Реальный, проверенный фикс одного из самых тяжёлых мест Metrostroi (пересчёт электропитания сети) — с мгновенным откатом и самодиагностикой на случай, если у конкретной сборки что-то пойдёт не так.
- Панель настроек (
rustcore_menu) — свет, список плагинов, управление профайлером, без консоли.
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).
- Собери (см. «Сборка» ниже) или возьми готовые бинарники из релиза.
.dll/.so→garrysmod/lua/bin/(и на сервере, и у каждого клиента — см. предупреждение выше).- Содержимое
lua_addon/lua/→garrysmod/lua/(или в отдельный аддонgarrysmod/addons/rustcore/lua/). - Плагины (
example_plugin.dll,light_plugin.dll) →garrysmod/rustcore_plugins/. - Проверка:
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 это уже форсирует - Кросс-компиляция под нужные таргеты:
Для сборки Windows-таргетов с Linux/macOS понадобится
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-gnucargo-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_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] диспетчер запущен
| Функция | Назначение |
|---|---|
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).
RustCore.Dispatch сам замеряет время каждого вызванного обработчика и,
если оно превышает порог (RustCore.SetSlowHookThreshold, по умолчанию
2 мс), печатает в консоль:
[RustCore] МЕДЛЕННЫЙ ХУК: 'Think' владельца 'example_addon' занял 4.13 мс
Отдельный инструмент, который оборачивает таймингом вообще все
зарегистрированные 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 поток.
StartTask/PollTasks асинхронны — результат приходит через кадр-другой.
RustCore.CallSync(kind, payload) — вызывает обработчик/плагин прямо в
текущем потоке, без очереди, результат мгновенно:
local ok, data = RustCore.CallSync("example_plugin", "какие-то данные")Осторожно: без потоков — любая медленная функция здесь напрямую
тормозит тик сервера/кадр клиента. Годится только быстрая чистая
математика, не сеть/диск. Для тяжёлой работы — StartTask.
RustCore.CallSyncParallel(items) — то же самое, но сразу несколько задач
параллельно (пул rayon), общее время = времени самой медленной задачи.
Плагин считает параметры для штатных экранных пост-эффектов 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 нужен на
клиенте у каждого игрока (клиентская фича).
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), по аналогии с серверным.
- Пресеты для панели света (сохранение/загрузка именованных наборов).