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

11 KiB

BuildArchiveWorker

Обзор класса

Смена сборки в лаунчере — это перекладывание содержимого .minecraft: текущее упаковывается в архив, папка вычищается, на её место распаковывается архив другой сборки. Речь о гигабайтах модов, конфигов и миров, и гонять их в потоке GUI нельзя — окно замерзало бы на всё время смены.

BuildArchiveWorker — исполнитель этих операций в отдельном потоке. Он умеет четыре вещи: упаковать, вычистить, распаковать и докатить пак сезонной сборки поверх уже разложенного содержимого.

Класс намеренно ничего не знает ни о сборках, ни о путях лаунчера: он принимает готовые пути и списки. Порядок шагов и восстановление после сбоя — дело BuildSwitcher.

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

Создаётся и целиком управляется BuildSwitcher, который же и переносит его в собственный 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 &currentPath)

Ход операции: сколько файлов обработано из скольких и какой обрабатывается сейчас. Испускается по ходу всех четырёх операций.

Обработчик — 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. Он создаёт поток и исполнителя, переносит второй в первый, вызывает слоты через очередь сигналов и принимает progress, finished и packEntries.

Пути и списки исключений BuildSwitcher берёт из launcherpaths.h и из описаний сборок; сам исполнитель к ним не обращается.

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

Файловая система. Класс читает, пишет и удаляет файлы в папке игры и в каталоге архивов сборок. Сетевых обращений и дочерних процессов у него нет: архив сезонной сборки к моменту вызова applyPack() уже скачан SeasonalPackDownloader.

Все операции выполняются в рабочем потоке, все сигналы приходят в поток GUI через очередь.

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

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));

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