documentation rewrite

Signed-off-by: galeon <dertiellis@gmail.com>
This commit is contained in:
galeon committed 2026-10-11 22:32:13 +03:00
1 parent 1943996608
commit f10b654953
34 files changed
+2099 -4576

No files matched your search

+49 -130
View File
@@ -1,152 +1,71 @@
# ModLoaderVersionService
## Обзор класса
`modloaderversionservice.h`, `modloaderversionservice.cpp`
Каждый модлоадер публикует свой список версий, и у каждого он устроен по-своему. `ModLoaderVersionService`
приводит все четыре к одному виду: качает списки, кэширует в папке лаунчера и отдаёт из кэша, пока
тот не устарел. Устроен так же, как [VersionManifestService](VersionManifestService.md).
Списки версий Forge, Fabric, NeoForge и Quilt. Устроен так же, как
[VersionManifestService](VersionManifestService.md): качает, хранит копию в
`<root>/cache/loader_<ключ>.json` и отвечает из неё, пока она моложе 6 часов.
Главная особенность класса — в том, чего в нём нет: отдельной проверки совместимости с версией
игры. Совместимость заложена в структуру данных. Fabric и Quilt отдают список сразу под нужную
версию игры, а `maven-metadata` Forge и NeoForge раскладывается по версиям игры при разборе.
Версии игры, под которую сборок нет, соответствует пустой список — выбрать несовместимый лоадер
физически нечем.
Совместимость с версией игры отдельно не проверяется: данные с самого начала разложены по версиям
игры. Под версию, для которой сборок нет, лежит пустой список, и выбрать несовместимый модлоадер
нечем.
## Место в проекте и зависимости
Экземпляр держит [LauncherBackend](LauncherBackend.md). [ModLoaderInstaller](ModLoaderInstaller.md)
берёт отсюда ссылку на `installer.jar` через `find()`.
Подключает [modloader.h](modloader.md): перечисление `ModLoader` и структура `LoaderVersionEntry`
приходят оттуда.
## Откуда берутся данные
Экземпляр создаётся и принадлежит [LauncherBackend](LauncherBackend.md). Ссылку на него получает
[ModLoaderInstaller](ModLoaderInstaller.md) — из записи списка он берёт адрес `installer.jar`.
**Fabric и Quilt** спрашиваются отдельно под каждую версию игры:
`meta.fabricmc.net/v2/versions/loader/<игра>` и `meta.quiltmc.org/v3/versions/loader/<игра>`.
Признак стабильности берётся из ответа, а если его нет, угадывается по имени.
Путь к файлу кэша даёт `LauncherPaths::loaderCacheFile()` из [launcherpaths.h](launcherpaths.md).
**Forge** — один `maven-metadata.xml` на все версии игры. Строка `1.20.1-47.4.0` делится по первому
дефису. Параллельно запрашивается `promotions_slim.json`, оттуда берутся отметки recommended. Если
он не ответил, список всё равно сохраняется, только без отметок.
Требования сборки: `Qt6::Core` (`QDateTime`, `QHash`, `QSet`) и `Qt6::Network`
(`QNetworkAccessManager`).
**NeoForge** тоже из `maven-metadata.xml`, но версия игры в номере не записана. Её приходится
выводить: `21.1.248` → `1.21.1`, `20.4.80` → `1.20.4`, `21.0.x` → `1.21`. Для четырёхчастных
номеров вроде `25.1.0.1` правило другое: `25.1.0.1` → `25.1`. Вторым запросом читается старый
репозиторий `net/neoforged/forge` с первыми сборками NeoForge под 1.20.1, которые выходили под
именем артефакта `forge` и номерами в стиле Forge.
## Иерархия и роль
Ссылка на установщик собирается по схеме maven:
`<репозиторий>/<версия>/<артефакт>-<версия>-installer.jar`.
Наследует `QObject`: мета-объектная система, два сигнала и владение по родителю. Виртуальных
методов базового класса не переопределяет.
Версии внутри каждой версии игры отсортированы от новых к старым. Числа сравниваются как числа,
поэтому `47.10` новее `47.9`.
## Псевдонимы типов
## ensureLoaded(loader, gameVersion, callback, forceRefresh)
`ModLoaderVersionService::Callback` — `std::function<void(bool ok, const QString &warning)>`.
Как и у сервиса манифеста, `ok == true` с непустым `warning` означает, что данные отдали из
устаревшего кэша.
Логика та же, что у манифеста версий. Отличие в том, что свежесть у Fabric и Quilt считается для
каждой версии игры отдельно, а у Forge и NeoForge для модлоадера целиком. По этому же ключу
объединяются одновременные запросы.
## Публичные методы
- Пустая `gameVersion` — сразу `ok` и пустой список.
- При первом обращении к модлоадеру читается кэш, и если он есть, испускается
`versionsChanged(key, gameVersion)`.
- Если сеть не ответила, но какие-то данные по модлоадеру есть, колбэк получит `ok = true` и
`warning`.
- Если ответ не разобрался, хороший кэш не затирается.
#### explicit ModLoaderVersionService(QObject \*parent = nullptr)
`versionsChanged(loaderKey, gameVersion)` испускается после каждого успешного обновления.
`refreshingChanged()` — в начале и в конце запроса. `isRefreshing(loader, gameVersion)` отвечает,
идёт ли запрос по этому ключу.
Создаёт сервис и его `QNetworkAccessManager`. Кэши читаются лениво, при первом обращении к
конкретному лоадеру. Конструктор помечен `explicit`.
## Формат кэша
#### void ensureLoaded(ModLoader loader, const QString &gameVersion, Callback callback, bool forceRefresh = false)
Единственная точка входа. Свежий кэш означает отложенный вызов колбэка без сети; иначе запускается
один сетевой запрос на всех, кто успел попросить.
Свежесть считается по-разному в зависимости от лоадера: Fabric и Quilt спрашиваются по каждой
версии игры отдельно, поэтому отметка времени у них своя на каждую версию; Forge и NeoForge
приходят одним `maven-metadata` на все версии сразу, и отметка у них одна на весь лоадер.
Параметр `forceRefresh` обходит проверку свежести.
#### QList&lt;LoaderVersionEntry&gt; versions(ModLoader loader, const QString &gameVersion) const
Список сборок лоадера под конкретную версию игры. Новые сборки идут первыми, поэтому первая строка
— самая свежая; именно её интерфейс подставляет по умолчанию.
Пустой список означает, что лоадер эту версию игры не поддерживает.
#### bool isRefreshing(ModLoader loader, const QString &gameVersion) const
Идёт ли сейчас запрос по этой паре. Интерфейс по этому признаку отличает «ещё грузим» от «не
поддерживается» — оба случая выглядят пустым списком.
#### std::optional&lt;LoaderVersionEntry&gt; 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](../qml/LoaderRow.md).
#### refreshingChanged()
Изменился признак сетевого обновления у какой-либо пары. Обработчик перечитывает
`isRefreshing()` для интересующей его пары.
## Владение и время жизни
Класс наследует `QObject` и принимает `parent` — родитель его и удалит.
`QNetworkAccessManager` создаётся в конструкторе с сервисом в роли родителя.
Отложенные колбэки хранятся по ключу запроса до завершения соответствующего обращения к сети.
Уничтожение сервиса с незавершённым запросом обрывает его, и накопленные колбэки не вызываются.
Ссылку на сервис держит установщик модлоадеров; уничтожать сервис раньше установщика нельзя.
## Потокобезопасность
Только поток GUI. Чтение и запись кэша выполняются синхронно в вызывающем потоке.
## Взаимодействие с другими классами
`LauncherBackend` оборачивает сервис тремя методами, доступными из QML: получить список, запросить
обновление и узнать, идёт ли загрузка. Сигнал `versionsChanged` он переправляет в QML под тем же
именем, поэтому строка лоадера в карточке сборки подписывается прямо на него.
[ModLoaderInstaller](ModLoaderInstaller.md) обращается к `find()` за адресом установщика перед
началом установки.
## Внешнее взаимодействие
**Сеть, исходящие запросы.** Класс обращается к четырём разным источникам метаданных, и форматы
ответов различаются: у Fabric и Quilt это JSON, у Forge и NeoForge — XML `maven-metadata`. Разбор
разделён на две функции соответственно.
Испорченный ответ разбирается в пустой результат, и хороший кэш им не затирается — это сознательное
решение: лучше показать вчерашний список, чем стереть его из-за сбоя на сервере.
При недоступной сети данные отдаются из устаревшего кэша с `ok == true` и заполненным `warning`.
Все сигналы и колбэки приходят в поток GUI.
## Пример использования
```cpp
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);
});
```json
{
"fetchedAt": "...",
"games": {
"1.20.1": { "fetchedAt": "...", "entries": [ { "loaderVersion": "47.4.0", ... } ] }
}
}
```
Верхний `fetchedAt` используется для Forge и NeoForge, `fetchedAt` внутри версии игры — для Fabric и
Quilt.
---
При создании этого документа использовался ИИ.