new docs for project
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
# BuildArchiveWorker
|
||||
|
||||
## Обзор класса
|
||||
|
||||
Смена сборки в лаунчере — это перекладывание содержимого `.minecraft`: текущее упаковывается в
|
||||
архив, папка вычищается, на её место распаковывается архив другой сборки. Речь о гигабайтах модов,
|
||||
конфигов и миров, и гонять их в потоке GUI нельзя — окно замерзало бы на всё время смены.
|
||||
|
||||
`BuildArchiveWorker` — исполнитель этих операций в отдельном потоке. Он умеет четыре вещи:
|
||||
упаковать, вычистить, распаковать и докатить пак сезонной сборки поверх уже разложенного
|
||||
содержимого.
|
||||
|
||||
Класс намеренно ничего не знает ни о сборках, ни о путях лаунчера: он принимает готовые пути и
|
||||
списки. Порядок шагов и восстановление после сбоя — дело [BuildSwitcher](BuildSwitcher.md).
|
||||
|
||||
## Место в проекте и зависимости
|
||||
|
||||
Создаётся и целиком управляется [BuildSwitcher](BuildSwitcher.md), который же и переносит его в
|
||||
собственный `QThread`. Больше к классу никто не обращается.
|
||||
|
||||
Требования сборки: `Qt6::Core` и `Qt6::CorePrivate` — последний нужен ради `QZipReader` и
|
||||
`QZipWriter`, которыми читаются и пишутся архивы сборок.
|
||||
|
||||
## Иерархия и роль
|
||||
|
||||
Наследует `QObject`: мета-объектная система, слоты, сигналы и возможность жить в отдельном потоке
|
||||
через `moveToThread()`. Виртуальных методов базового класса не переопределяет.
|
||||
|
||||
Объект живёт в своём `QThread` и вызывается только через очередь сигналов — прямых вызовов его
|
||||
слотов из потока GUI быть не должно.
|
||||
|
||||
## Публичные методы
|
||||
|
||||
#### explicit BuildArchiveWorker(QObject \*parent = nullptr)
|
||||
|
||||
Создаёт исполнителя. Конструктор помечен `explicit`.
|
||||
|
||||
#### void requestCancel()
|
||||
|
||||
Просит прервать текущую операцию. Вызывается из потока GUI прямо во время работы — это
|
||||
единственный метод класса, предназначенный для вызова снаружи рабочего потока.
|
||||
|
||||
Отмена не прерывает операцию мгновенно: признак проверяется между файлами. Хранится он в
|
||||
`QAtomicInt`, поэтому запись из одного потока и чтение из другого безопасны без блокировок.
|
||||
|
||||
#### void clearCancel()
|
||||
|
||||
Сбрасывает признак отмены перед началом новой операции.
|
||||
|
||||
## Публичные слоты
|
||||
|
||||
Все четыре слота вызываются через очередь сигналов и выполняются в рабочем потоке. Каждый
|
||||
завершается сигналом `finished`.
|
||||
|
||||
#### void archive(const QString &gameDir, const QString &tempZipPath, const QStringList &excludeTop)
|
||||
|
||||
Упаковывает всё содержимое `gameDir` во временный файл `tempZipPath`. Элементы верхнего уровня,
|
||||
перечисленные в `excludeTop`, в архив не попадают — так из архива сборки исключаются общие
|
||||
каталоги лаунчера, которые не принадлежат ни одной сборке.
|
||||
|
||||
Запись идёт во временный файл, чтобы прерванная упаковка не оставила повреждённый архив на месте
|
||||
настоящего.
|
||||
|
||||
#### void clear(const QString &gameDir, const QStringList &keepTop)
|
||||
|
||||
Удаляет из `gameDir` всё, кроме элементов верхнего уровня, перечисленных в `keepTop`. Выполняется
|
||||
после упаковки, перед распаковкой другой сборки.
|
||||
|
||||
#### void restore(const QString &zipPath, const QString &gameDir)
|
||||
|
||||
Распаковывает архив сборки в `gameDir`.
|
||||
|
||||
#### void applyPack(const QString &zipPath, const QString &gameDir, const QStringList &removeRelative, const QStringList &forbiddenTop)
|
||||
|
||||
Докатывает пак сезонной сборки поверх уже разложенного содержимого. Сначала удаляет файлы из
|
||||
`removeRelative` — те, что ушли из сборки в новой ревизии, — затем распаковывает архив с
|
||||
перезаписью.
|
||||
|
||||
Того, чего нет ни в списке, ни в архиве, операция не касается: миры и скриншоты игрока остаются на
|
||||
месте. Именно это отличает обновление сезонной сборки от её переустановки.
|
||||
|
||||
Параметр `forbiddenTop` — элементы верхнего уровня, которые паку трогать нельзя: общие каталоги
|
||||
лаунчера. Проверяется здесь, а не только у издателя пака, потому что архив приезжает из сети.
|
||||
|
||||
## Сигналы
|
||||
|
||||
#### progress(int done, int total, const QString ¤tPath)
|
||||
|
||||
Ход операции: сколько файлов обработано из скольких и какой обрабатывается сейчас. Испускается по
|
||||
ходу всех четырёх операций.
|
||||
|
||||
Обработчик — `BuildSwitcher` — пересчитывает долю выполнения и обновляет панель прогресса. Сигнал
|
||||
приходит в поток GUI через очередь, поэтому прямого доступа к состоянию рабочего потока у
|
||||
обработчика нет.
|
||||
|
||||
#### finished(bool ok, const QString &error)
|
||||
|
||||
Операция завершена. При `ok == false` во втором параметре — текст ошибки.
|
||||
|
||||
Обработчик решает, что делать дальше: перейти к следующему шагу смены сборки или откатить уже
|
||||
сделанное.
|
||||
|
||||
#### packEntries(const QStringList &entries)
|
||||
|
||||
Что именно принёс пак — относительными путями. Испускается только из `applyPack()`.
|
||||
|
||||
Список сохраняется в описании сезонной сборки: следующему обновлению он нужен, чтобы вычислить,
|
||||
какие файлы из сборки ушли, и передать их в `removeRelative`.
|
||||
|
||||
## Владение и время жизни
|
||||
|
||||
Класс наследует `QObject` и принимает `parent`, но на практике родителя не имеет: объект,
|
||||
перенесённый в другой поток через `moveToThread()`, не может иметь родителя в потоке GUI.
|
||||
Ответственность за его удаление лежит на `BuildSwitcher`, который создаёт и поток, и исполнителя.
|
||||
|
||||
Удалять объект следует безопасным для потоков способом — не напрямую из потока GUI во время
|
||||
работы. Уничтожение потока раньше исполнителя приведёт к обрыву незавершённой операции.
|
||||
|
||||
## Потокобезопасность
|
||||
|
||||
Класс рассчитан на жизнь в отдельном потоке. Все четыре слота выполняются в рабочем потоке;
|
||||
вызывать их напрямую нельзя — только через очередь сигналов.
|
||||
|
||||
Единственная точка межпотокового взаимодействия — признак отмены в `QAtomicInt`:
|
||||
`requestCancel()` и `clearCancel()` пишут его из потока GUI, а рабочий поток читает между файлами.
|
||||
Другого разделяемого состояния у класса нет, поэтому иных блокировок не требуется.
|
||||
|
||||
## Взаимодействие с другими классами
|
||||
|
||||
Единственный собеседник — [BuildSwitcher](BuildSwitcher.md). Он создаёт поток и исполнителя,
|
||||
переносит второй в первый, вызывает слоты через очередь сигналов и принимает `progress`,
|
||||
`finished` и `packEntries`.
|
||||
|
||||
Пути и списки исключений `BuildSwitcher` берёт из [launcherpaths.h](launcherpaths.md) и из
|
||||
описаний сборок; сам исполнитель к ним не обращается.
|
||||
|
||||
## Внешнее взаимодействие
|
||||
|
||||
**Файловая система.** Класс читает, пишет и удаляет файлы в папке игры и в каталоге архивов
|
||||
сборок. Сетевых обращений и дочерних процессов у него нет: архив сезонной сборки к моменту вызова
|
||||
`applyPack()` уже скачан [SeasonalPackDownloader](SeasonalPackDownloader.md).
|
||||
|
||||
Все операции выполняются в рабочем потоке, все сигналы приходят в поток GUI через очередь.
|
||||
|
||||
## Пример использования
|
||||
|
||||
```cpp
|
||||
auto *thread = new QThread(this);
|
||||
auto *worker = new BuildArchiveWorker; // без родителя: уедет в другой поток
|
||||
worker->moveToThread(thread);
|
||||
connect(thread, &QThread::finished, worker, &QObject::deleteLater);
|
||||
thread->start();
|
||||
|
||||
connect(worker, &BuildArchiveWorker::progress, this, &Switcher::onProgress);
|
||||
connect(worker, &BuildArchiveWorker::finished, this, &Switcher::onStepFinished);
|
||||
|
||||
worker->clearCancel();
|
||||
QMetaObject::invokeMethod(worker, "archive", Qt::QueuedConnection,
|
||||
Q_ARG(QString, gameDir),
|
||||
Q_ARG(QString, tempZipPath),
|
||||
Q_ARG(QStringList, excludeTop));
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
При создании этого документа использовался ИИ.
|
||||
Reference in New Issue
Block a user