Skip to content

Latest commit

 

History

History
119 lines (70 loc) · 9.38 KB

File metadata and controls

119 lines (70 loc) · 9.38 KB

Архитектура: жизненный цикл мода

От момента, когда .jar положили в mods/, до момента, когда Minecraft закрылся.

Состояния

DESCUBIERTO  →  RESUELTO  →  CARGADO  →  INICIADO  →  ACTIVO  →  DETENIDO

Явный enum — dev.vida.base.EstadoMod. Переход односторонний; исключение — DETENIDO, после которого мод больше не участвует в жизненном цикле до перезапуска JVM.

DESCUBIERTO

Кандидат найден ModScanner'ом. Считается, что у него валидный JAR, есть vida.mod.json, и манифест прошёл парсинг. В этом состоянии мод — просто ModCandidate: метаданные + источник. Классов ещё никто не грузил.

RESUELTO

Резолвер включил мода в финальную Resolution. Все его жёсткие зависимости нашлись, совместимость с другими модами сошлась. Опциональные зависимости могут быть как найдены, так и отсутствовать — резолвер их не откладывает.

CARGADO

Для мода создан отдельный ModLoader (classloading.md). Классы мода загружены в него, но iniciar(...) ещё не вызван. В этом состоянии мод уже может быть «увиден» через рефлексию, но его контент ещё не в Catalogo, и он ещё не подписан на Latidos.

INICIADO

Vida создала экземпляр VidaMod через no-args-конструктор и вызвала iniciar(ModContext). После успешного возврата метода мод находится в INICIADO. В этой фазе:

  • Мод должен зарегистрировать весь свой контент в Catalogo.
  • Мод должен подписаться на все интересующие его Latidos.
  • Мод не должен выполнять тяжёлые вычисления — это тормозит старт игры для всех остальных.

ACTIVO

Игра запущена, главное меню или мир загружены. Мод получает события, его обработчики вызываются, его контент виден в игре. Это штатное состояние.

DETENIDO

Vida вызвала detener(ModContext). Мод должен был остановить все свои потоки, закрыть файлы, освободить буферы. После этого экземпляр мода становится мусором; Vida теряет ссылку на него.

Гарантии

Ордер между модами

Внутри одной фазы жизненного цикла порядок модов определяется топологической сортировкой графа зависимостей:

  • Если мод B объявил dependencies.required.A, то A.iniciar(...) будет вызван до B.iniciar(...).
  • При отсутствии явной зависимости — детерминированный порядок по алфавиту id, чтобы сборки были воспроизводимы.

Типизированные фазы (LatidoFaseCiclo)

Дополнительно к состояниям EstadoMod загрузчик эмитит LatidoFaseCiclo с FaseCicloMod в фиксированном порядке: PREPARACIONINICIALIZACIONPOST_INICIALIZACION, затем LatidoArranque. Порядок для двух модов с разными приоритетами подписок фиксируется интеграционным тестом BootLifecyclePhasesTest.

Thread-affinity

Все методы жизненного цикла вызываются из одного и того же потока — основного потока инициализации Vida. Это не «главный поток Minecraft» (его ещё нет), но контракт аналогичен: не блокировать, не ждать I/O.

Атомарность

Если iniciar(...) мода бросает exception, Vida:

  1. Логирует полный stack trace в уровне ERROR.
  2. Помечает мод как FAILED.
  3. Моды, которые depend on failed-мода, не инициализируются и тоже получают FAILED.
  4. Моды, которые ни напрямую, ни транзитивно не зависят от упавшего, проходят инициализацию штатно.

То есть одна ошибка не роняет всю инициализацию, но и не замалчивается. Отчёт о FAILED-модах доступен через BootRegistry.

Последовательность в Minecraft-терминах

Vida не предписывает никаких доменных хуков («PRE_INIT», «SERVER_STARTING») на уровне жизненного цикла — это задача Latidos. Загрузчик отвечает только за то, чтобы:

  • все моды вышли в INICIADO до того, как JVM начнёт исполнять код Minecraft,
  • из ACTIVO в DETENIDO переход синхронизирован с shutdown-hook'ом JVM.

События игровой логики (тик, spawn мира, загрузка чанка) — отдельные LatidoTipo в модуле base. Подробно — guides/latidos.md.

Что делает detener

Штатные причины вызова:

  1. Shutdown JVM. Vida регистрирует Runtime.getRuntime().addShutdownHook(...), который идёт по активным модам в обратном топологическом порядке и зовёт detener(ctx) для каждого.
  2. Dev hot-reload. DSL vida.run { hotReload.set(true) } / JVM-флаги (docs/guides/hot-reload.md) включают watcher и сброс CatalogoManejador в dev; не используется в production и не отменяет ограничение «без retransform на горячем пути» из performance.md.

В реализации detener нужно:

  • остановить все thread-pool'ы, созданные модом,
  • отписаться от Latidos (или положиться на авто-cleanup — бывает, когда ModLoader уходит на GC, но это гарантия «best effort»),
  • закрыть AutoCloseable-ресурсы.

Дефолтная реализация — no-op. Переопределяйте только если моду реально есть что освобождать.

Что не делать

  • Не делайте сетевых запросов из iniciar(...). Это тормозит старт игры для всех. Если нужно — запустите фоновой задачей и подпишитесь на LatidoArranque (готовность клиента), чтобы применить результат тогда.
  • Не подписывайтесь дважды. Latidos не дедуплицирует подписки. Если вы видите IllegalStateException: already subscribed — вызывайте latidos.suscribir(...) ровно один раз.
  • Не трогайте ModContext из фоновых потоков. Он thread-safe для читающих операций, но запрос ctx.catalogos().obtener(...) после перехода в ACTIVO возвращает замороженный реестр — любая попытка registrar(...) бросит IllegalStateException.

Диагностика

Во время boot-а Vida пишет в лог точечный INFO-отчёт:

[vida.loader] descubiertos: 17, resueltos: 15, iniciados: 15, fallos: 2
  fallo miaventura/0.2.0 (RESUELTO→INICIADO): java.lang.NoSuchMethodError: ...
  fallo otromod/1.1.0 (RESUELTO→INICIADO): java.lang.OutOfMemoryError

Плюс полные stack trace в уровне ERROR. Если видите, что у вас «17 → 15», сначала посмотрите отчёт резолвера (RESUELTO→INICIADO против DESCUBIERTO→RESUELTO — разные причины).

Тестирование

VidaBoot.boot(options) с кастомной директорией модов даёт полный lifecycle в integration-тесте. Проверяйте:

  1. Что ваш мод доходит до INICIADO.
  2. Что ctx.log() реально пишет в test-appender.
  3. Что ваши регистрации в Catalogo видны извне.

Пример — в installer/src/test/java/... (там похожий pattern). Собственный test-suite для мода — через vidaTestRun-задачу Gradle (preview).