Split the platform out of Core: new Host library + HeadlessHost
CI / build-test (push) Successful in 1m24s

Core now depends only on Friflo.Engine.ECS — no MonoGame, no platform.
The windowed MonoGame host moves to the new MrGameEng.Host library:
GameHost, GameHostOptions, visual scene transitions (OverlayTransition,
Transitions.Fade/Wipe, TransitionRenderer) and the Input feature
(namespace MrGameEng.Input is unchanged). Transition timing stays in
Core (SceneManager exposes ActiveTransition/TransitionCoverage/
TransitionPhase; the host draws the overlay). EngineContext loses its
GraphicsDevice property: hosts publish the device as a service and
graphics code reads it via context.GetGraphicsDevice() in Graphics.

Core gains HeadlessHost: a fixed-timestep loop without a window or GPU
(Tick/RunTicks, Run with wall-clock pacing and lag resync) for dedicated
servers, batch simulation and tests. Graphics and Audio now carry their
own MonoGame.Framework.DesktopGL reference instead of inheriting it
from Core.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Leonid Pershin
2026-06-12 23:04:34 +03:00
co-authored by Claude Fable 5
parent 79d406a9f4
commit 2ac074004a
33 changed files with 560 additions and 184 deletions
+25 -13
View File
@@ -33,7 +33,8 @@
| Библиотека (сборка) | Фичи (неймспейсы) и ответственность |
|------------------------------|------------------------------------------------------------|
| `MrGameEng.Core` | Игровой цикл (хост над `Game`), `EntityStore`, `SystemRoot`, сцены, время (`GameClock.TimeScale`; `GameSpeed` — дискретная скорость пауза/1×/3×/6× поверх часов; `Calendar` — игровые дни поверх масштабированного времени, `context.UseCalendar(...)`; `Climate` — непрерывная сезонная/суточная температура и сезон поверх календаря, `context.UseClimate(...)`), жизненный цикл. **Input** (`MrGameEng.Input`, `Core/Input/`): клавиатура, мышь, геймпад, action maps |
| `MrGameEng.Core` | Платформо-независимое ядро (зависит только от Friflo): `EngineContext`, `EntityStore`, `SystemRoot`, сцены и переходы (тайминг — `Transition`, `SceneManager`; визуал переходов — в `Host`), время (`GameClock.TimeScale`; `GameSpeed` — дискретная скорость пауза/1×/3×/6× поверх часов; `Calendar` — игровые дни поверх масштабированного времени, `context.UseCalendar(...)`; `Climate` — непрерывная сезонная/суточная температура и сезон поверх календаря, `context.UseClimate(...)`), жизненный цикл, `ServiceRegistry`, `Log`. **`HeadlessHost`** — цикл без окна и GPU на фиксированном тике (`Tick`/`RunTicks`/`Run` с realtime-пейсингом): дедикейтед-серверы, батч-симуляция, тесты |
| `MrGameEng.Host` | Оконный MonoGame-хост: `GameHost` (обёртка над `Game` — цикл, окно, `GraphicsDeviceManager`; публикует `GraphicsDevice` сервисом в контексте), визуальные переходы сцен (`OverlayTransition`, фабрики `Transitions.Fade/Wipe`, `TransitionRenderer`). **Input** (`MrGameEng.Input`, `Host/Input/`): клавиатура, мышь, геймпад, action maps |
| `MrGameEng.Graphics` | Собственный батчер-рендерер (см. «Рендеринг»), камера, спрайты, анимации, слои. **Tilemaps** (`MrGameEng.Tilemaps`, `Graphics/Tilemaps/`): тайловые карты кодом — `TileGrid` + `TileSet` + компонент `Tilemap`, отрисовка видимых клеток через батчер. **Lighting** (`MrGameEng.Lighting`, `Graphics/Lighting/`): амбиент день/ночь поверх `Calendar``Renderer2D.AmbientLight`, `scene.UseDayNight(renderer)`; по-клеточный лайтмап — `LightmapBuilder` (амбиент × окклюзия + точечные `PointLight` с трассировкой теней), накладывается multiply поверх мира через `scene.UseLighting(...)`, сэмплируется `Lighting.SampleAt` |
| `MrGameEng.Audio` | Звуковые эффекты и музыка (NVorbis); `AudioManager` с `SoundVolume`/`MasterVolume` (одна ручка на эффекты и музыку) |
| `MrGameEng.Content` | Пайплайн контента. **Assets** (`MrGameEng.Assets`): runtime-загрузка без Content Pipeline, кэш, `AssetRef<T>`. **Atlases** (`MrGameEng.Atlases`): текстурные атласы — сборка из дерева картинок (`AtlasBuilder`) и рантайм-загрузка (`TextureAtlas`), CLI `tools/MrGameEng.AtlasTool`. **Mods** (`MrGameEng.Mods`): система модов — порядок загрузки, JSON-дефы, локализация, слияние деревьев контента |
@@ -69,19 +70,27 @@
### Правило зависимостей
```
MrGameEng.Audio ─┐
MrGameEng.Graphics ─┼──► MrGameEng.Core ──► MonoGame.Framework.DesktopGL
│ └──► Friflo.Engine.ECS
MrGameEng.Host ─┐
MrGameEng.Audio ─┤
MrGameEng.Graphics ─┼──► MrGameEng.Core ──► Friflo.Engine.ECS
MrGameEng.Content ─┤
MrGameEng.Simulation ─┤ (Content — за Texture2DRegion,
MrGameEng.UI ─┴──► MrGameEng.Graphics Simulation/UI — за Transform2D/рендер)
```
Библиотека зависит **только от `Core` и `Graphics`**. `Core` зависит только от MonoGame
и Friflo. Если двум библиотекам нужен общий тип — он переезжает в `Core` (или, если это
графический тип, в `Graphics`). `Content` тянет `Graphics` (атласы выдают
`Texture2DRegion`); `Simulation` тянет `Graphics` (`Collisions` использует `Transform2D`,
`RectF`); `UI` — только `Core` (Myra рисует своим SpriteBatch).
Библиотека зависит **только от `Core` и `Graphics`**. `Core` зависит только от Friflo —
ни MonoGame, ни другой платформы: благодаря этому симуляция запускается и без окна
(`HeadlessHost`). MonoGame (`MonoGame.Framework.DesktopGL`) тянут платформенные и
графические библиотеки: `Host`, `Graphics`, `Audio` (и транзитивно их потребители).
Платформенные ресурсы (например `GraphicsDevice`) хосты публикуют сервисами в
`EngineContext.Services`; графический код достаёт девайс через
`context.GetGraphicsDevice()` (расширение в `Graphics`). На `Host` не зависит никто,
кроме самой игры — это точка входа оконной платформы. Если двум библиотекам нужен общий
тип — он переезжает в `Core` (или, если это графический тип, в `Graphics`). `Content`
тянет `Graphics` (атласы выдают `Texture2DRegion`); `Simulation` тянет `Graphics`
(`Collisions` использует `Transform2D`, `RectF`); `UI` — только `Core` (Myra рисует
своим SpriteBatch).
Фичи, собранные в одну библиотеку, делят её набор пакетов (например, `Content` несёт и
FontStash, и StbImage). Тяжёлые/опциональные зависимости (Myra, NVorbis) держим в
@@ -345,12 +354,15 @@ CLI-обёртка: `dotnet run --project tools/MrGameEng.AtlasTool -- <исто
- `SceneManager` владеет активной сценой; обычное переключение откладывается до начала
следующего кадра (сцена никогда не выгружается посреди собственного кадра).
- `Scenes.Switch(scene, Transition.Fade(0.5f))` — переключение с визуальным переходом:
- `Scenes.Switch(scene, Transitions.Fade(0.5f))` — переключение с визуальным переходом:
фаза закрытия (старая сцена живёт) → своп при полном покрытии → фаза открытия.
Тяжёлый `OnLoad` новой сцены скрыт за полностью закрытым экраном.
- Встроенные переходы: `Transition.Fade(duration, color)` и `Transition.Wipe(duration, color)`
(шторка). Свои — наследованием от `Transition` (рисование через `TransitionRenderer.Fill`
в нормализованных координатах экрана).
- Тайминг-машина (`Transition` — длительности фаз, покрытие) живёт в `Core` и работает
и в headless-контексте; визуал — в `Host`: встроенные `Transitions.Fade(duration, color)`
и `Transitions.Wipe(duration, color)` (шторка). Свои — наследованием от
`OverlayTransition` (рисование через `TransitionRenderer.Fill` в нормализованных
координатах экрана); `GameHost` рисует оверлей поверх сцены, читая
`Scenes.ActiveTransition`/`TransitionCoverage`/`TransitionPhase`.
- Переходы идут по **unscaled**-времени: работают при паузе геймплея (`TimeScale = 0`).
- Повторный `Switch` во время перехода заменяет целевую сцену, не перезапуская переход.