ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Ely Prism Launcher 任务进度体系:SequentialTask 多阶段任务总进度条实现指南

Ely Prism Launcher 任务进度体系:SequentialTask 多阶段任务总进度条实现指南

【免费下载链接】Launcher

This fork of Prism Launcher adds integrated support for Ely.by accounts.

项目地址:https://gitcode.com/GitHub_Trending/ely/Launcher
点击查看免费下载

在 Ely Prism Launcher(Prism Launcher 的 Ely.by 账号整合分支)中,新建实例下载游戏文件、更新模组、检查资源更新等操作都依赖一套统一的任务进度体系。本文拆解其中的核心类SequentialTask:它如何把多个子任务串成一条流水线,并把"第 X 步 / 共 N 步"实时反映为界面上的总进度条,帮你快速读懂源码、扩展自己的多阶段任务。

一图看懂任务体系:Task → ConcurrentTask → SequentialTask

整个进度体系建立在三层继承关系上(源码位于 launcher/tasks/):

层级类职责
基类Task定义状态机(Inactive/Running/Succeeded/Failed)、进度信号progress、状态文本status
中间层ConcurrentTask维护子任务队列,按max_concurrent并发调度
特化层SequentialTask将并发数固定为1,实现严格顺序执行

基类Task通过 Qt 信号把进度广播出去,任何界面只需connect几个信号就能画出进度条,见 launcher/tasks/Task.h:

  • progress(current, total):总进度数值,驱动进度条
  • status / details:状态与详情文本
  • stepProgress(TaskStepProgress):每个子任务的独立进度,驱动"子任务进度列表"
  • succeeded / failed / aborted:终态通知

SequentialTask 如何实现"一个一个来"

SequentialTask的实现极其巧妙——它并没有重写调度逻辑,而是直接复用ConcurrentTask,把并发上限设为 1(launcher/tasks/SequentialTask.cpp):

SequentialTask::SequentialTask(QString task_name) : ConcurrentTask(task_name, 1) {}

父类ConcurrentTask内部用三个容器管理子任务:m_queue(排队)、m_doing(执行中)、m_done(已完成)。核心调度方法executeNextSubTask在"执行中数量达到上限"时直接返回,上限为 1 时自然形成串行队列;前一个子任务完成后会再次触发调度,取出下一个(launcher/tasks/ConcurrentTask.cpp)。

总进度条的推进规则:按"完成的阶段数"计算

这是总进度条最关键的一点。SequentialTask重写了updateState()(launcher/tasks/SequentialTask.cpp):

setProgress(m_done.count(), totalSize()); setStatus(tr("Executing task %1 out of %2")...);

也就是说,总进度 =已完成子任务数 / 子任务总数。每完成一个阶段,进度条前进一格,状态文本显示"Executing task 2 out of 5"。父类的updateState还有单任务特判:当只有一个子任务时,直接把该子任务的字节级进度透传给父进度条,避免"假进度"(launcher/tasks/ConcurrentTask.cpp)。

快速失败:任何一步出错,整条链立刻终止

SequentialTask覆写了subTaskFailed:子任务一失败就立即emitFailed(msg),不再执行后续阶段(launcher/tasks/SequentialTask.cpp)。这符合"后一步依赖前一步结果"的语义——比如资源没下载完,就不能做下一步的校验。

实战案例:新建实例时游戏文件的总进度条

在创建 Minecraft 实例并下载游戏文件时,InstanceCreationTask构造了一个典型的SequentialTask(launcher/InstanceCreationTask.cpp):

  1. addTask(MinecraftLoadAndCheck):先加载并校验版本清单
  2. 再依次addTask四个更新阶段,来自 MinecraftInstance::createUpdateTask:
    • FoldersTask:创建实例目录
    • LibrariesTask:下载 Java 库
    • LegacyFMLLibrariesTask:处理老版 Forge 库
    • AssetUpdateTask:更新游戏资源
  3. 调用propagateFromOther(task.get()),把子任务的 status/progress/stepProgress 全部桥接到外层任务,这样上层ProgressDialog无需感知内部结构

用户看到的就是:顶部总进度条按 5 个阶段推进,状态栏提示当前执行到第几步。

另一个直接继承SequentialTask的自定义任务见 launcher/minecraft/mod/tasks/GetModDependenciesTask.h,它按"先查项目信息、再取依赖清单"的顺序依次拉取 Modrinth / Flame 平台的模组元数据。

进度弹窗如何渲染这套信号

界面端由 launcher/ui/dialogs/ProgressDialog.cpp 负责:

  • execWithTask统一连接progress、status、stepProgress等信号
  • 根据task->isMultiStep()决定是否显示子任务进度滚动区(launcher/ui/dialogs/ProgressDialog.cpp)
  • changeStepProgress为每个子任务动态创建一个SubTaskProgressBar小进度条,子任务完成后自动隐藏(launcher/ui/dialogs/ProgressDialog.cpp),小进度条组件定义在 launcher/ui/widgets/SubTaskProgressBar.h

于是总进度条(globalProgressBar)与逐条子进度条形成了"宏观 + 微观"的双层反馈。

与 ConcurrentTask、MultipleOptionsTask 的选型对比

任务容器并发度失败行为适用场景
ConcurrentTask默认 6 路并发汇总所有失败原因独立文件并行下载
SequentialTask1(严格串行)首个失败即终止有依赖关系的阶段链
MultipleOptionsTask1(串行)继续尝试下一个,直到成功多候选源依次兜底,见 launcher/tasks/MultipleOptionsTask.h

单元测试test_basicSequentialRun验证了三个子任务按序全部成功的路径(tests/Task_test.cpp),可作为扩展新任务时的回归参照。

源码文件速查 📚

  • 基类与进度信号:launcher/tasks/Task.h
  • 顺序执行特化:launcher/tasks/SequentialTask.h
  • 并发调度与状态更新:launcher/tasks/ConcurrentTask.cpp
  • 实例创建串接更新任务:launcher/InstanceCreationTask.cpp
  • 更新阶段清单:launcher/minecraft/MinecraftInstance.cpp
  • 进度弹窗渲染:launcher/ui/dialogs/ProgressDialog.cpp

理解这套体系后你会发现:新增一个多阶段任务只需继承SequentialTask,按依赖顺序addTask,再交给ProgressDialog展示——总进度条、状态文本、子任务明细全部自动完成。

【免费下载链接】Launcher

This fork of Prism Launcher adds integrated support for Ely.by accounts.

项目地址:https://gitcode.com/GitHub_Trending/ely/Launcher
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进