GameFramework——Download篇

目录

一、架构一览

二、快速入门

1.初始化与事件监听

[2. 添加下载任务](#2. 添加下载任务)

[3. 控制下载任务](#3. 控制下载任务)

[4. 查询任务信息](#4. 查询任务信息)

[5. 自定义下载代理辅助器](#5. 自定义下载代理辅助器)

三、源码分析

[1. TaskPool](#1. TaskPool)

[2. DownloadTask 与 DownloadAgent](#2. DownloadTask 与 DownloadAgent)

[3. UnityWebRequestDownloadAgentHelper 实现细节](#3. UnityWebRequestDownloadAgentHelper 实现细节)

[4. DownloadCounter 滑动窗口计速器](#4. DownloadCounter 滑动窗口计速器)

[5. DownloadManager](#5. DownloadManager)

6.事件传递流程


一、架构一览

Download 模块DownloadManager 统一管理,内部使用 TaskPool 实现任务队列和代理调度,支持优先级排序和断点续传;同时借助 DownloadCounter 基于滑动窗口计算实时平均下载速度。

模块通过抽象基类 DownloadAgentHelperBase 和接口 IDownloadAgentHelper 允许用户自定义底层网络实现(如 UnityWebRequestHttpClient),极大增强了扩展性。此外,该模块内部已经实现了常用的下载事件参数,可供外部注册。

该模块在资源模块(Resource)中被大量用于资源热更新,同时在 Unity 项目中可通过 DownloadComponent 组件快速使用。

二、快速入门

1.初始化与事件监听

cs 复制代码
using GameFramework;
using GameFramework.Download;
using UnityEngine;
using UnityGameFramework.Runtime;

public class DownloadTest : MonoBehaviour
{
    private DownloadComponent m_DownloadComponent;
    private EventComponent m_EventComponent;

    private void Start()
    {
        // 获取组件
        m_DownloadComponent = GameEntry.GetComponent<DownloadComponent>();
        m_EventComponent = GameEntry.GetComponent<EventComponent>();

        // 订阅下载事件
        m_EventComponent.Subscribe(DownloadStartEventArgs.EventId, OnDownloadStart);
        m_EventComponent.Subscribe(DownloadUpdateEventArgs.EventId, OnDownloadUpdate);
        m_EventComponent.Subscribe(DownloadSuccessEventArgs.EventId, OnDownloadSuccess);
        m_EventComponent.Subscribe(DownloadFailureEventArgs.EventId, OnDownloadFailure);
    }

    private void OnDownloadStart(object sender, GameEventArgs e)
    {
        DownloadStartEventArgs ne = e as DownloadStartEventArgs;
        Debug.Log($"下载开始:序列号 {ne.SerialId},路径 {ne.DownloadPath}");
    }

    private void OnDownloadUpdate(object sender, GameEventArgs e)
    {
        DownloadUpdateEventArgs ne = e as DownloadUpdateEventArgs;
        // 可以在此更新 UI 进度
    }

    private void OnDownloadSuccess(object sender, GameEventArgs e)
    {
        DownloadSuccessEventArgs ne = e as DownloadSuccessEventArgs;
        Debug.Log($"下载成功:序列号 {ne.SerialId},保存至 {ne.DownloadPath}");
    }

    private void OnDownloadFailure(object sender, GameEventArgs e)
    {
        DownloadFailureEventArgs ne = e as DownloadFailureEventArgs;
        Debug.LogError($"下载失败:序列号 {ne.SerialId},错误 {ne.ErrorMessage}");
    }
}

2. 添加下载任务

cs 复制代码
// 最简单的调用
int serialId = m_DownloadComponent.AddDownload(
    Application.persistentDataPath + "/test.zip",
    "http://example.com/test.zip"
);

// 带标签、优先级和自定义数据
int serialId2 = m_DownloadComponent.AddDownload(
    Application.persistentDataPath + "/res.zip",
    "http://example.com/res.zip",
    "res_update",    // 标签,可用于批量操作
    10,              // 优先级(数值越大越优先)
    "myUserData"     // 自定义数据,会在事件中返回
);

3. 控制下载任务

cs 复制代码
// 暂停/恢复所有下载
m_DownloadComponent.Paused = true;   // 暂停
m_DownloadComponent.Paused = false;  // 恢复

// 移除单个任务
bool removed = m_DownloadComponent.RemoveDownload(serialId);

// 移除所有带某标签的任务
int removedCount = m_DownloadComponent.RemoveDownloads("res_update");

// 移除所有任务
int allRemoved = m_DownloadComponent.RemoveAllDownloads();

4. 查询任务信息

cs 复制代码
// 根据序列号获取信息
TaskInfo info = m_DownloadComponent.GetDownloadInfo(serialId);
if (info != null)
{
    Debug.Log($"状态:{info.Status},已下载:{info.CurrentLength}");
}

// 根据标签获取信息列表
TaskInfo[] infos = m_DownloadComponent.GetDownloadInfos("res_update");
foreach (var i in infos)
{
    Debug.Log($"任务 {i.SerialId}:{i.DownloadUri}");
}

// 获取所有任务
TaskInfo[] all = m_DownloadComponent.GetAllDownloadInfos();

5. 自定义下载代理辅助器

cs 复制代码
using GameFramework.Download;
using UnityEngine;
using UnityGameFramework.Runtime;

public class MyDownloadAgentHelper : DownloadAgentHelperBase
{
    // 实现抽象方法,使用自己的网络请求
    public override void Download(string downloadUri, object userData)
    {
        // 自定义实现
    }

    public override void Download(string downloadUri, long fromPosition, object userData)
    {
        // 自定义实现(断点续传)
    }

    public override void Download(string downloadUri, long fromPosition, long toPosition, object userData)
    {
        // 自定义实现(范围下载)
    }

    public override void Reset()
    {
        // 重置状态
    }

    // 触发事件时,通过事件参数传递数据
    // 示例:
    // DownloadAgentHelperUpdateBytesEventArgs e = DownloadAgentHelperUpdateBytesEventArgs.Create(data, offset, length);
    // DownloadAgentHelperUpdateBytes?.Invoke(this, e);
}

三、源码分析

DownloadManager 核心数据结构与工作流程

1. TaskPool<DownloadTask>

DownloadManager 内部持有 TaskPool<DownloadTask> 类型成员 m_TaskPool
TaskPool<T> 是一个泛型类,泛型约束为 TaskBase。其核心字段:

  • private readonly Stack<ITaskAgent<T>> m_FreeAgents空闲代理栈,存放可用的任务代理。

  • private readonly LinkedList<ITaskAgent<T>> m_WorkingAgents工作中代理链表,存放正在执行任务的代理。

  • private readonly LinkedList<T> m_WaitingTasks等待任务链表,存放尚未分配代理的任务。

  • private bool m_Paused:暂停标志。

主要方法

  • Update(elapseSeconds, realElapseSeconds):任务池轮询入口。

    • 先调用 ProcessRunningTasks:遍历 m_WorkingAgents,检查每个代理的任务是否完成(Task.Done)。若完成,则重置代理、移入 m_FreeAgents,并释放任务(归还引用池);否则调用代理的 Update 方法。

    • 再调用 ProcessWaitingTasks:遍历 m_WaitingTasks,尝试从 m_FreeAgents 弹出空闲代理。若成功,则将代理移入 m_WorkingAgents,调用 agent.Start(task) 开始执行;若无可空闲代理则跳出循环。

  • AddAgent(ITaskAgent<T> agent):增加任务代理。调用代理的 Initialize 方法,并将其压入 m_FreeAgents

  • AddTask(T task):添加任务。根据 task.Priority(数值越大优先级越高)插入到 m_WaitingTasks 的合适位置。

  • RemoveTask(int serialId):根据序列号移除任务。会分别在 m_WaitingTasksm_WorkingAgents 中查找,若在工作链表中找到,则重置对应代理并移入空闲栈,最后释放任务。

  • RemoveAllTasks / GetAllTaskInfos 等辅助方法。

2. DownloadTaskDownloadAgent

  • DownloadTask 继承 TaskBase,实现 IReference。包含字段:
    DownloadTaskStatus m_Status(Todo / Doing / Done / Error)、
    string m_DownloadPath(下载后存放路径)、
    string m_DownloadUri(下载地址)、
    int m_FlushSize(缓冲区刷盘临界大小)、
    float m_Timeout(超时秒数)、
    object m_UserData(自定义数据)。

    通过 Create 静态方法从引用池获取实例。

  • DownloadAgent 实现 ITaskAgent<DownloadTask> 接口。核心字段:
    IDownloadAgentHelper m_Helper(外部注入的辅助器,负责底层网络)、
    DownloadTask m_Task(当前处理的任务)、
    FileStream m_FileStream(写入临时文件的流)、
    int m_WaitFlushSize(累计未刷盘数据)、
    float m_WaitTime(等待数据的时间,用于超时检测)、
    long m_StartLength(续传起始偏移)、
    long m_DownloadedLength(本次下载量)、
    long m_SavedLength(已落盘数据量)。

主要方法:

  • Initialize():为 m_Helper 注册事件(UpdateBytesUpdateLengthCompleteError)。

  • Update():若任务状态为 Doing,累加 m_WaitTime,超时则触发错误事件。

  • Start(DownloadTask task)

    1. 确定临时文件路径 {DownloadPath}.download

    2. 若文件存在则以追加模式打开,记录 m_StartLength 为已有长度;否则创建新文件。

    3. 触发 DownloadAgentStart 委托。

    4. m_StartLength > 0,调用 m_Helper.Download(uri, fromPosition, userData) 实现断点续传;否则调用普通下载。

  • Reset() / Dispose():关闭文件流、清空状态、释放资源。

3. UnityWebRequestDownloadAgentHelper 实现细节

  • 继承 DownloadAgentHelperBase,内部使用 UnityWebRequest 执行下载。

  • 内部嵌套类 DownloadHandler 继承 DownloadHandlerScript,重写 ReceiveData(byte[] data, int dataLength) 方法。

    在该方法中触发 DownloadAgentHelperUpdateBytesDownloadAgentHelperUpdateLength 事件,将接收到的原始数据及长度向上传递(更新字段、写入文件流)。

  • Update() 方法中检测 UnityWebRequest 的完成状态,触发 CompleteError 事件。

4. DownloadCounter 滑动窗口计速器

  • 内部类 DownloadCounterNode 记录一个时间片段内的下载字节数(m_DeltaLength)和经过时间(m_ElapseSeconds)。

  • DownloadCounter 维护一个 LinkedList<DownloadCounterNode> 作为滑动窗口。

  • 速度计算逻辑 :将时间划分为固定长度(UpdateInterval)的片段,每个片段用一个 DownloadCounterNode 记录该片段内累计的下载字节数,从而方便后续滑动窗口计算速度。

    • 每次收到增量数据时相应事件调用 RecordDeltaLength(int deltaLength):将增量加到当前滑动窗口最右端节点(若当前节点存在且未超过 UpdateInterval),否则创建新节点并加入链表末尾。

    • 每帧调用 Update

      1. 遍历更新所有节点的 ElapseSeconds,移除超时节点(ElapseSeconds超过 RecordInterval)。

      2. m_TimeLeft 倒计时归零时(默认每 UpdateInterval 秒一次),计算窗口内总下载字节和总时间(m_Accumulator,但最大不超过 RecordInterval),得到 CurrentSpeed = totalBytes / totalTime,然后重置倒计时。

    • m_Accumulator 会限制最大为 RecordInterval,确保速度计算只考虑最近一个窗口。

5. DownloadManager

Update(elapseSeconds, realElapseSeconds) 中依次调用:

cs 复制代码
m_TaskPool.Update(elapseSeconds, realElapseSeconds);
m_DownloadCounter.Update(elapseSeconds, realElapseSeconds);

Shutdown() 中调用:

cs 复制代码
m_TaskPool.Shutdown();
m_DownloadCounter.Shutdown();

​​​​​​AddDownloadAgentHelper(IDownloadAgentHelper helper):创建 DownloadAgent,订阅其 DownloadAgentStartUpdateSuccessFailure 委托,然后调用 m_TaskPool.AddAgent(agent)

AddDownload(...):通过 DownloadTask.Create 获取任务,调用 m_TaskPool.AddTask(task),返回任务的序列号。

6.事件传递流程

相关推荐
ab1237682 小时前
C++ size() 与 length() 核心笔记
开发语言·c++·笔记
记录无知岁月2 小时前
【学习笔记】学术英语单词总结
学习·accumulation·english word
xiaoxiaoxiaolll2 小时前
《Light》刊发北大新成果:谐振耦合架构实现超宽带孤子微梳,能效创纪录
学习
mxwin2 小时前
Unity URP 下的 Early-Z / Depth Prepass 解决复杂片元着色器造成的 Overdraw 问题
unity·游戏引擎·着色器
Xudde.2 小时前
班级作业笔记报告0x06
笔记·学习·安全·web安全
不早睡不改名@2 小时前
Netty源码解析---FastThreadLocal-addToVariablesToRemove方法详解
java·网络·笔记·学习·netty
CDA数据分析师干货分享2 小时前
【访谈】食品专业转行数据分析师,CDA数据分析师二级备考经验
学习·信息可视化·数据分析·cda证书·cda数据分析师
盐城吊霸天2 小时前
Spring AI + Flux/FluxSink + SSE 实战技术笔记
人工智能·笔记·spring
猿汁猿味yyds2 小时前
java学习day-15 集合、ArrayList集合
学习