Files
minecraft-launcher/doc/cpp/ModLoaderVersionService.md
2026-09-03 09:16:56 +03:00

9.5 KiB
Raw Permalink Blame History

ModLoaderVersionService

Обзор класса

Каждый модлоадер публикует свой список версий, и у каждого он устроен по-своему. ModLoaderVersionService приводит все четыре к одному виду: качает списки, кэширует в папке лаунчера и отдаёт из кэша, пока тот не устарел. Устроен так же, как VersionManifestService.

Главная особенность класса — в том, чего в нём нет: отдельной проверки совместимости с версией игры. Совместимость заложена в структуру данных. Fabric и Quilt отдают список сразу под нужную версию игры, а maven-metadata Forge и NeoForge раскладывается по версиям игры при разборе. Версии игры, под которую сборок нет, соответствует пустой список — выбрать несовместимый лоадер физически нечем.

Место в проекте и зависимости

Подключает modloader.h: перечисление ModLoader и структура LoaderVersionEntry приходят оттуда.

Экземпляр создаётся и принадлежит LauncherBackend. Ссылку на него получает ModLoaderInstaller — из записи списка он берёт адрес installer.jar.

Путь к файлу кэша даёт LauncherPaths::loaderCacheFile() из launcherpaths.h.

Требования сборки: Qt6::Core (QDateTime, QHash, QSet) и Qt6::Network (QNetworkAccessManager).

Иерархия и роль

Наследует QObject: мета-объектная система, два сигнала и владение по родителю. Виртуальных методов базового класса не переопределяет.

Псевдонимы типов

ModLoaderVersionService::Callbackstd::function<void(bool ok, const QString &warning)>. Как и у сервиса манифеста, ok == true с непустым warning означает, что данные отдали из устаревшего кэша.

Публичные методы

explicit ModLoaderVersionService(QObject *parent = nullptr)

Создаёт сервис и его QNetworkAccessManager. Кэши читаются лениво, при первом обращении к конкретному лоадеру. Конструктор помечен explicit.

void ensureLoaded(ModLoader loader, const QString &gameVersion, Callback callback, bool forceRefresh = false)

Единственная точка входа. Свежий кэш означает отложенный вызов колбэка без сети; иначе запускается один сетевой запрос на всех, кто успел попросить.

Свежесть считается по-разному в зависимости от лоадера: Fabric и Quilt спрашиваются по каждой версии игры отдельно, поэтому отметка времени у них своя на каждую версию; Forge и NeoForge приходят одним maven-metadata на все версии сразу, и отметка у них одна на весь лоадер.

Параметр forceRefresh обходит проверку свежести.

QList<LoaderVersionEntry> versions(ModLoader loader, const QString &gameVersion) const

Список сборок лоадера под конкретную версию игры. Новые сборки идут первыми, поэтому первая строка — самая свежая; именно её интерфейс подставляет по умолчанию.

Пустой список означает, что лоадер эту версию игры не поддерживает.

bool isRefreshing(ModLoader loader, const QString &gameVersion) const

Идёт ли сейчас запрос по этой паре. Интерфейс по этому признаку отличает «ещё грузим» от «не поддерживается» — оба случая выглядят пустым списком.

std::optional<LoaderVersionEntry> find(ModLoader loader, const QString &gameVersion, const QString &loaderVersion) const

Запись по версии лоадера; std::nullopt, если такой нет. Из неё установщик берёт ссылку на installer.jar.

Сигналы

versionsChanged(const QString &loaderKey, const QString &gameVersion)

Список версий изменился. Параметры сужают событие до конкретной пары: loaderKey принимает значения forge, fabric, neoforge, quilt.

Обработчик должен сверить оба параметра со своим текущим состоянием и перечитать versions(), только если они совпадают, — иначе обновление относится к другой строке лоадера. Именно так поступает LoaderRow.

refreshingChanged()

Изменился признак сетевого обновления у какой-либо пары. Обработчик перечитывает isRefreshing() для интересующей его пары.

Владение и время жизни

Класс наследует QObject и принимает parent — родитель его и удалит. QNetworkAccessManager создаётся в конструкторе с сервисом в роли родителя.

Отложенные колбэки хранятся по ключу запроса до завершения соответствующего обращения к сети. Уничтожение сервиса с незавершённым запросом обрывает его, и накопленные колбэки не вызываются.

Ссылку на сервис держит установщик модлоадеров; уничтожать сервис раньше установщика нельзя.

Потокобезопасность

Только поток GUI. Чтение и запись кэша выполняются синхронно в вызывающем потоке.

Взаимодействие с другими классами

LauncherBackend оборачивает сервис тремя методами, доступными из QML: получить список, запросить обновление и узнать, идёт ли загрузка. Сигнал versionsChanged он переправляет в QML под тем же именем, поэтому строка лоадера в карточке сборки подписывается прямо на него.

ModLoaderInstaller обращается к find() за адресом установщика перед началом установки.

Внешнее взаимодействие

Сеть, исходящие запросы. Класс обращается к четырём разным источникам метаданных, и форматы ответов различаются: у Fabric и Quilt это JSON, у Forge и NeoForge — XML maven-metadata. Разбор разделён на две функции соответственно.

Испорченный ответ разбирается в пустой результат, и хороший кэш им не затирается — это сознательное решение: лучше показать вчерашний список, чем стереть его из-за сбоя на сервере.

При недоступной сети данные отдаются из устаревшего кэша с ok == true и заполненным warning.

Все сигналы и колбэки приходят в поток GUI.

Пример использования

auto *loaders = new ModLoaderVersionService(this);

connect(loaders, &ModLoaderVersionService::versionsChanged,
        this, [this](const QString &key, const QString &game) {
            if (key == loaderKey(ModLoader::Fabric) && game == m_gameVersion)
                emit fabricVersionsChanged();
        });

loaders->ensureLoaded(ModLoader::Fabric, QStringLiteral("1.21.1"),
                      [this, loaders](bool ok, const QString &warning) {
                          if (!ok) {
                              showStatus(warning);
                              return;
                          }
                          const auto list = loaders->versions(ModLoader::Fabric,
                                                              QStringLiteral("1.21.1"));
                          if (!list.isEmpty())
                              selectVersion(list.first().loaderVersion);
                      });

При создании этого документа использовался ИИ.