От момента, когда .jar положили в mods/, до момента, когда Minecraft закрылся.
DESCUBIERTO → RESUELTO → CARGADO → INICIADO → ACTIVO → DETENIDO
Явный enum — dev.vida.base.EstadoMod. Переход односторонний; исключение — DETENIDO, после которого мод больше не участвует в жизненном цикле до перезапуска JVM.
Кандидат найден ModScanner'ом. Считается, что у него валидный JAR, есть vida.mod.json, и манифест прошёл парсинг. В этом состоянии мод — просто ModCandidate: метаданные + источник. Классов ещё никто не грузил.
Резолвер включил мода в финальную Resolution. Все его жёсткие зависимости нашлись, совместимость с другими модами сошлась. Опциональные зависимости могут быть как найдены, так и отсутствовать — резолвер их не откладывает.
Для мода создан отдельный ModLoader (classloading.md). Классы мода загружены в него, но iniciar(...) ещё не вызван. В этом состоянии мод уже может быть «увиден» через рефлексию, но его контент ещё не в Catalogo, и он ещё не подписан на Latidos.
Vida создала экземпляр VidaMod через no-args-конструктор и вызвала iniciar(ModContext). После успешного возврата метода мод находится в INICIADO. В этой фазе:
- Мод должен зарегистрировать весь свой контент в
Catalogo. - Мод должен подписаться на все интересующие его
Latidos. - Мод не должен выполнять тяжёлые вычисления — это тормозит старт игры для всех остальных.
Игра запущена, главное меню или мир загружены. Мод получает события, его обработчики вызываются, его контент виден в игре. Это штатное состояние.
Vida вызвала detener(ModContext). Мод должен был остановить все свои потоки, закрыть файлы, освободить буферы. После этого экземпляр мода становится мусором; Vida теряет ссылку на него.
Внутри одной фазы жизненного цикла порядок модов определяется топологической сортировкой графа зависимостей:
- Если мод B объявил
dependencies.required.A, тоA.iniciar(...)будет вызван доB.iniciar(...). - При отсутствии явной зависимости — детерминированный порядок по алфавиту
id, чтобы сборки были воспроизводимы.
Дополнительно к состояниям EstadoMod загрузчик эмитит LatidoFaseCiclo с FaseCicloMod в фиксированном порядке: PREPARACION → INICIALIZACION → POST_INICIALIZACION, затем LatidoArranque. Порядок для двух модов с разными приоритетами подписок фиксируется интеграционным тестом BootLifecyclePhasesTest.
Все методы жизненного цикла вызываются из одного и того же потока — основного потока инициализации Vida. Это не «главный поток Minecraft» (его ещё нет), но контракт аналогичен: не блокировать, не ждать I/O.
Если iniciar(...) мода бросает exception, Vida:
- Логирует полный stack trace в уровне
ERROR. - Помечает мод как
FAILED. - Моды, которые depend on failed-мода, не инициализируются и тоже получают
FAILED. - Моды, которые ни напрямую, ни транзитивно не зависят от упавшего, проходят инициализацию штатно.
То есть одна ошибка не роняет всю инициализацию, но и не замалчивается. Отчёт о FAILED-модах доступен через BootRegistry.
Vida не предписывает никаких доменных хуков («PRE_INIT», «SERVER_STARTING») на уровне жизненного цикла — это задача Latidos. Загрузчик отвечает только за то, чтобы:
- все моды вышли в
INICIADOдо того, как JVM начнёт исполнять код Minecraft, - из
ACTIVOвDETENIDOпереход синхронизирован с shutdown-hook'ом JVM.
События игровой логики (тик, spawn мира, загрузка чанка) — отдельные LatidoTipo в модуле base. Подробно — guides/latidos.md.
Штатные причины вызова:
- Shutdown JVM. Vida регистрирует
Runtime.getRuntime().addShutdownHook(...), который идёт по активным модам в обратном топологическом порядке и зовётdetener(ctx)для каждого. - 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-тесте. Проверяйте:
- Что ваш мод доходит до
INICIADO. - Что
ctx.log()реально пишет в test-appender. - Что ваши регистрации в
Catalogoвидны извне.
Пример — в installer/src/test/java/... (там похожий pattern). Собственный test-suite для мода — через vidaTestRun-задачу Gradle (preview).