Files
h-school/docs/design/runtime.md
T

67 lines
5.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Рантайм школы: поток и ECS
Договорённость на ближайшее планирование. Сейчас в коде **не так**: один `GameLoopService`
тикает все школы на одном потоке, и это записано в `AGENTS.md` как инвариант. Ниже — целевая
модель. Менять инвариант в рабочих соглашениях имеет смысл только вместе с кодом.
Экран и defs: [`near-term.md`](near-term.md), [`defs.md`](defs.md).
Куда класть код: [`projects.md`](projects.md).
Работник (поток, очередь, сейв-файл) живёт в **Server**. То, что он тикает — `School` в
**Simulation**. Каталог, с которым школа создана — **Content**, замороженный у работника.
## Что уже почти есть
У каждой `School` уже свой Arch `World`. Это оставляем и делаем жёстким правилом: мир не
разделяется между школами и не отдаётся чужому потоку. Arch не потокобезопасен.
## Целевая модель — в этом срезе
Каждая школа — **актор**: свой поток (`TaskCreationOptions.LongRunning` / выделенный поток, не
пул тиков), свой `World`, свои часы, свой **замороженный** каталог def, свой файл на диске.
Над ними — тонкий супервизор (вместо сегодняшнего общего цикла):
- создать / удалить школу, знать лимит;
- держать ящики: `id →` очередь команд этой школы;
- собирать **снимки** для `GET /api/schools` (работник публикует неизменяемое состояние, как
сейчас цикл публикует `SchoolsState`);
- маршрутизировать сокет: `OpenSchool` / `SetRunning` / кадры часов — только в ящик той школы;
- при старте процесса поднять школы с диска, при остановке — дождаться записи работников.
Супервизор **не** вызывает `World`, не тикает часы, не читает defs инстанса. Работник **не**
трогает чужой мир и не ходит в ASP.NET. Файл школы пишет **работник** (create/delete через
команду супервизору, снимок часов — редкий, shutdown — обязательный).
Тик по-прежнему фиксированный (`SimulationOptions.FixedDeltaTime`), у каждого работника свой
таймер. Школы не синхронизируют календарь друг с другом — так и задумано.
Пока школ максимум шесть, шесть потоков — нормально. Не ставить тик симуляции на `ThreadPool`:
под нагрузкой его заберут запросы и часы начнут плыть.
## Зачем ломать текущий цикл
Один поток на все школы дешевле и проще (нет гонок между мирами). Отдельный поток нужен, когда
школы перестанут быть «только часы»: моды, карта, ECS. Тогда одна тяжёлая школа не должна
останавливать остальные, и набор модов у школы A не должен быть глобальным синглтоном процесса.
Каталог def **на школу**, не на процесс: при создании выбирают моды, работник грузит папки и
больше их не перечитывает. Иначе правка JSON на диске внезапно меняет уже идущую школу.
## Как это стыкуется с сокетом и меню
Как сейчас по смыслу, другое только «кто трогает School»:
- входящее с HTTP/WS → команда в ящик школы (или супервизору, если это create/delete);
- меню читает опубликованный снимок, без блокировки работника;
- исходящие кадры часов — из работника в outbox соединения, не наоборот.
Инвариант вместо «только поток цикла трогает registry» становится: **только работник школы
трогает её `School` / `World` / каталог; супервизор трогает только таблицу ящиков.**
## Пока не делаем
- Потоки на системы внутри одной школы (job system как у RimWorld).
- Миграция живой школы на другой набор модов.
- Запись сейва на каждом тике.