mirror of
https://github.com/tuyoogame/YooAsset.git
synced 2026-05-26 10:40:14 +00:00
Compare commits
10 Commits
2.3.18
...
f0563cce0b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f0563cce0b | ||
|
|
9b83dcf723 | ||
|
|
ee67a55c0f | ||
|
|
454afc9ba6 | ||
|
|
539ca3523e | ||
|
|
c87efdb509 | ||
|
|
1884fab0c2 | ||
|
|
e5d0a856a5 | ||
|
|
5da8c6baf8 | ||
|
|
33356cb270 |
945
Assets/YooAsset/Runtime/DiagnosticSystem/README.md
Normal file
945
Assets/YooAsset/Runtime/DiagnosticSystem/README.md
Normal file
@@ -0,0 +1,945 @@
|
|||||||
|
# DiagnosticSystem 诊断系统
|
||||||
|
|
||||||
|
## 模块概述
|
||||||
|
|
||||||
|
DiagnosticSystem 是 YooAsset 的**远程调试诊断系统**,提供运行时资源管理状态的实时可视化和性能分析能力。该系统通过编辑器窗口与运行时游戏进行双向通信,实时采集和展示资源加载、Bundle 管理、异步操作等调试信息。
|
||||||
|
|
||||||
|
### 核心特性
|
||||||
|
|
||||||
|
- **实时远程调试**:在 Unity 编辑器中查看游戏运行时的资源管理状态
|
||||||
|
- **完整状态快照**:采集所有资源、Bundle、异步操作的实时信息
|
||||||
|
- **历史数据回溯**:缓存最近 500 帧数据,支持时间回溯分析
|
||||||
|
- **双模式采样**:支持单次采样和自动连续采样
|
||||||
|
- **低性能开销**:按需采样,无需连续监控
|
||||||
|
|
||||||
|
### 模块统计
|
||||||
|
|
||||||
|
| 组件 | 职责 |
|
||||||
|
|------|------|
|
||||||
|
| 核心通信 | RemoteDebuggerInRuntime + 双连接层 |
|
||||||
|
| 数据结构 | 5 个调试信息结构体 |
|
||||||
|
| 命令系统 | RemoteCommand 命令定义 |
|
||||||
|
| **总计** | 10 个核心文件,完整的远程诊断框架 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 设计目标
|
||||||
|
|
||||||
|
| 目标 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| **实时可视化** | 在编辑器中实时查看运行时资源状态 |
|
||||||
|
| **性能监控** | 收集加载耗时、引用计数、下载进度等性能指标 |
|
||||||
|
| **内存诊断** | 追踪资源出生场景、引用计数,识别潜在内存泄漏 |
|
||||||
|
| **操作追踪** | 显示异步操作树,包括嵌套和依赖关系 |
|
||||||
|
| **低开销设计** | 按需采样而非连续监控,DEBUG 模式自动启用 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 文件结构
|
||||||
|
|
||||||
|
```
|
||||||
|
DiagnosticSystem/
|
||||||
|
├── RemoteDebuggerDefine.cs # 全局定义和常量
|
||||||
|
├── RemoteCommand.cs # 命令定义和序列化
|
||||||
|
├── DebugReport.cs # 调试报告(顶层容器)
|
||||||
|
├── DebugPackageData.cs # 包级调试数据
|
||||||
|
├── DebugProviderInfo.cs # 资源加载器调试信息
|
||||||
|
├── DebugBundleInfo.cs # 资源包调试信息
|
||||||
|
├── DebugOperationInfo.cs # 异步操作调试信息
|
||||||
|
├── RemoteDebuggerInRuntime.cs # 运行时调试器主类
|
||||||
|
├── RemotePlayerConnection.cs # 编辑器模拟连接层
|
||||||
|
└── RemoteEditorConnection.cs # 运行时模拟连接层
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 核心类说明
|
||||||
|
|
||||||
|
### RemoteDebuggerDefine
|
||||||
|
|
||||||
|
全局定义类,包含调试器版本和通信协议的 GUID 标识符。
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
internal class RemoteDebuggerDefine
|
||||||
|
{
|
||||||
|
// 调试器版本(用于版本校验)
|
||||||
|
public const string DebuggerVersion = "2.3.3";
|
||||||
|
|
||||||
|
// 消息标识符(GUID)
|
||||||
|
public static readonly Guid kMsgPlayerSendToEditor =
|
||||||
|
new Guid("e34a5702dd353724aa315fb8011f08c3"); // 运行时→编辑器
|
||||||
|
|
||||||
|
public static readonly Guid kMsgEditorSendToPlayer =
|
||||||
|
new Guid("4d1926c9df5b052469a1c63448b7609a"); // 编辑器→运行时
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### RemoteCommand
|
||||||
|
|
||||||
|
命令定义类,用于编辑器向运行时发送采样指令。
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
internal enum ERemoteCommand
|
||||||
|
{
|
||||||
|
SampleOnce = 0, // 单次采样
|
||||||
|
SampleAuto = 1, // 自动采样(连续)
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
internal class RemoteCommand
|
||||||
|
{
|
||||||
|
public int CommandType; // ERemoteCommand 枚举值
|
||||||
|
public string CommandParam; // 命令参数
|
||||||
|
|
||||||
|
// 序列化/反序列化(JSON 格式)
|
||||||
|
public static byte[] Serialize(RemoteCommand command)
|
||||||
|
{
|
||||||
|
return Encoding.UTF8.GetBytes(JsonUtility.ToJson(command));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RemoteCommand Deserialize(byte[] data)
|
||||||
|
{
|
||||||
|
return JsonUtility.FromJson<RemoteCommand>(Encoding.UTF8.GetString(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**命令示例:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
// 单次采样
|
||||||
|
{
|
||||||
|
"CommandType": 0,
|
||||||
|
"CommandParam": ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开启自动采样
|
||||||
|
{
|
||||||
|
"CommandType": 1,
|
||||||
|
"CommandParam": "open"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭自动采样
|
||||||
|
{
|
||||||
|
"CommandType": 1,
|
||||||
|
"CommandParam": "close"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### DebugReport
|
||||||
|
|
||||||
|
调试报告容器,包含完整的系统状态快照。
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
[Serializable]
|
||||||
|
internal class DebugReport
|
||||||
|
{
|
||||||
|
// 调试器版本(用于版本校验)
|
||||||
|
public string DebuggerVersion = RemoteDebuggerDefine.DebuggerVersion;
|
||||||
|
|
||||||
|
// 游戏帧数
|
||||||
|
public int FrameCount;
|
||||||
|
|
||||||
|
// 包级调试数据列表(一个游戏可能有多个资源包)
|
||||||
|
public List<DebugPackageData> PackageDatas = new List<DebugPackageData>(10);
|
||||||
|
|
||||||
|
// 序列化/反序列化
|
||||||
|
public static byte[] Serialize(DebugReport debugReport);
|
||||||
|
public static DebugReport Deserialize(byte[] data);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### DebugPackageData
|
||||||
|
|
||||||
|
包级调试数据,包含单个资源包的所有诊断信息。
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
[Serializable]
|
||||||
|
internal class DebugPackageData
|
||||||
|
{
|
||||||
|
// 资源包名称
|
||||||
|
public string PackageName;
|
||||||
|
|
||||||
|
// 资源加载器列表
|
||||||
|
public List<DebugProviderInfo> ProviderInfos = new List<DebugProviderInfo>(1000);
|
||||||
|
|
||||||
|
// 资源包列表
|
||||||
|
public List<DebugBundleInfo> BundleInfos = new List<DebugBundleInfo>(1000);
|
||||||
|
|
||||||
|
// 异步操作列表
|
||||||
|
public List<DebugOperationInfo> OperationInfos = new List<DebugOperationInfo>(1000);
|
||||||
|
|
||||||
|
// 运行时查询字典(非序列化,按需构建)
|
||||||
|
[NonSerialized]
|
||||||
|
public Dictionary<string, DebugBundleInfo> BundleInfoDic;
|
||||||
|
|
||||||
|
// 延迟解析字典
|
||||||
|
public DebugBundleInfo GetBundleInfo(string bundleName);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### DebugProviderInfo
|
||||||
|
|
||||||
|
资源加载器(Provider)的调试信息。
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
[Serializable]
|
||||||
|
internal struct DebugProviderInfo : IComparer<DebugProviderInfo>, IComparable<DebugProviderInfo>
|
||||||
|
{
|
||||||
|
public string PackageName; // 所属包名
|
||||||
|
public string AssetPath; // 资源路径(如 "Assets/Prefabs/Player.prefab")
|
||||||
|
public string SpawnScene; // 资源加载时的活跃场景名
|
||||||
|
public string BeginTime; // 加载开始时间(格式:HH:mm:ss.fff)
|
||||||
|
public long LoadingTime; // 加载耗时(单位:毫秒)
|
||||||
|
public int RefCount; // 引用计数
|
||||||
|
public string Status; // 加载状态(None/Processing/Succeed/Failed)
|
||||||
|
public List<string> DependBundles; // 依赖的资源包名列表
|
||||||
|
|
||||||
|
// 按 AssetPath 字母排序
|
||||||
|
public int CompareTo(DebugProviderInfo other);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**关键诊断价值:**
|
||||||
|
- `SpawnScene`:追踪资源在哪个场景被加载,帮助识别资源泄漏
|
||||||
|
- `LoadingTime`:性能分析,识别加载慢的资源
|
||||||
|
- `RefCount`:引用计数监控,RefCount > 0 表示资源仍在使用
|
||||||
|
- `DependBundles`:依赖分析,理解资源加载的完整依赖链
|
||||||
|
|
||||||
|
### DebugBundleInfo
|
||||||
|
|
||||||
|
资源包(Bundle)的调试信息。
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
[Serializable]
|
||||||
|
internal struct DebugBundleInfo : IComparer<DebugBundleInfo>, IComparable<DebugBundleInfo>
|
||||||
|
{
|
||||||
|
public string BundleName; // 资源包名称
|
||||||
|
public int RefCount; // 引用计数(当前有多少个 Provider 在使用)
|
||||||
|
public string Status; // 加载状态
|
||||||
|
public List<string> ReferenceBundles; // 反向依赖(谁引用了我)
|
||||||
|
|
||||||
|
// 按 BundleName 字母排序
|
||||||
|
public int CompareTo(DebugBundleInfo other);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**关键诊断价值:**
|
||||||
|
- `RefCount`:Bundle 引用计数,为 0 时可以被卸载
|
||||||
|
- `ReferenceBundles`:反向依赖分析,了解 Bundle 被哪些其他 Bundle 依赖
|
||||||
|
|
||||||
|
### DebugOperationInfo
|
||||||
|
|
||||||
|
异步操作的调试信息,支持递归树结构。
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
[Serializable]
|
||||||
|
internal struct DebugOperationInfo : IComparer<DebugOperationInfo>, IComparable<DebugOperationInfo>
|
||||||
|
{
|
||||||
|
public string OperationName; // 操作类名(如 "LoadAssetOperation")
|
||||||
|
public string OperationDesc; // 操作说明(自定义描述)
|
||||||
|
public uint Priority; // 优先级(用于操作排序)
|
||||||
|
public float Progress; // 进度(0.0 - 1.0)
|
||||||
|
public string BeginTime; // 操作开始时间
|
||||||
|
public long ProcessTime; // 处理耗时(单位:毫秒)
|
||||||
|
public string Status; // 操作状态(None/Processing/Succeed/Failed)
|
||||||
|
public List<DebugOperationInfo> Childs; // 子操作列表(支持嵌套树结构)
|
||||||
|
|
||||||
|
public int CompareTo(DebugOperationInfo other);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**递归树结构示例:**
|
||||||
|
```
|
||||||
|
InitializationOperation
|
||||||
|
├─ LoadManifestOperation
|
||||||
|
│ ├─ LoadBundleFileOperation (manifest.bundle)
|
||||||
|
│ └─ DeserializeManifestOperation
|
||||||
|
└─ InitFileSystemOperation
|
||||||
|
```
|
||||||
|
|
||||||
|
**关键诊断价值:**
|
||||||
|
- `OperationName`:操作类型识别
|
||||||
|
- `ProcessTime`:性能瓶颈分析
|
||||||
|
- `Childs`:操作依赖关系可视化
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 通信系统
|
||||||
|
|
||||||
|
### RemoteDebuggerInRuntime
|
||||||
|
|
||||||
|
运行时调试器主类,负责接收命令、采样数据、发送报告。
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
internal class RemoteDebuggerInRuntime : MonoBehaviour
|
||||||
|
{
|
||||||
|
// 采样控制标志
|
||||||
|
private static bool _sampleOnce = false; // 单次采样
|
||||||
|
private static bool _autoSample = false; // 连续采样
|
||||||
|
|
||||||
|
// 运行时初始化
|
||||||
|
[RuntimeInitializeOnLoadMethod]
|
||||||
|
private static void RuntimeInitializeOnLoad()
|
||||||
|
{
|
||||||
|
_sampleOnce = false;
|
||||||
|
_autoSample = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
RemotePlayerConnection.Instance.Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
// 注册命令接收回调
|
||||||
|
RemotePlayerConnection.Instance.Register(
|
||||||
|
RemoteDebuggerDefine.kMsgEditorSendToPlayer,
|
||||||
|
OnHandleEditorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LateUpdate()
|
||||||
|
{
|
||||||
|
// 采样逻辑(在一帧的最后执行)
|
||||||
|
if (_autoSample || _sampleOnce)
|
||||||
|
{
|
||||||
|
_sampleOnce = false;
|
||||||
|
var debugReport = YooAssets.GetDebugReport();
|
||||||
|
var data = DebugReport.Serialize(debugReport);
|
||||||
|
|
||||||
|
RemotePlayerConnection.Instance.Send(
|
||||||
|
RemoteDebuggerDefine.kMsgPlayerSendToEditor,
|
||||||
|
data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnHandleEditorMessage(MessageEventArgs args)
|
||||||
|
{
|
||||||
|
var command = RemoteCommand.Deserialize(args.data);
|
||||||
|
|
||||||
|
if (command.CommandType == (int)ERemoteCommand.SampleOnce)
|
||||||
|
{
|
||||||
|
_sampleOnce = true;
|
||||||
|
}
|
||||||
|
else if (command.CommandType == (int)ERemoteCommand.SampleAuto)
|
||||||
|
{
|
||||||
|
_autoSample = (command.CommandParam == "open");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**关键设计点:**
|
||||||
|
1. **LateUpdate 时机**:确保该帧所有资源加载完成后再采样
|
||||||
|
2. **状态重置**:`[RuntimeInitializeOnLoadMethod]` 确保编辑器重新编译时重置状态
|
||||||
|
3. **DEBUG 模式自动启用**:通过 `#if DEBUG` 条件编译自动添加组件
|
||||||
|
|
||||||
|
### 双连接层架构
|
||||||
|
|
||||||
|
YooAsset 支持两种通信模式:
|
||||||
|
|
||||||
|
| 模式 | 使用场景 | 实现方式 |
|
||||||
|
|------|----------|----------|
|
||||||
|
| **编辑器模拟模式** | 开发调试 | `RemotePlayerConnection` + `RemoteEditorConnection`(虚拟连接) |
|
||||||
|
| **发布版本** | 运营期监控 | Unity 的 `PlayerConnection` API(真实网络) |
|
||||||
|
|
||||||
|
#### RemotePlayerConnection(编辑器模拟模式)
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
internal class RemotePlayerConnection
|
||||||
|
{
|
||||||
|
private static RemotePlayerConnection _instance;
|
||||||
|
private readonly Dictionary<Guid, UnityAction<MessageEventArgs>> _messageCallbacks;
|
||||||
|
|
||||||
|
public static RemotePlayerConnection Instance
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_instance == null)
|
||||||
|
_instance = new RemotePlayerConnection();
|
||||||
|
return _instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Register(Guid messageID, UnityAction<MessageEventArgs> callback)
|
||||||
|
{
|
||||||
|
_messageCallbacks.Add(messageID, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Send(Guid messageID, byte[] data)
|
||||||
|
{
|
||||||
|
// 在编辑器模拟模式下,发送给虚拟编辑器连接
|
||||||
|
RemoteEditorConnection.Instance.HandlePlayerMessage(messageID, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void HandleEditorMessage(Guid messageID, byte[] data)
|
||||||
|
{
|
||||||
|
if (_messageCallbacks.TryGetValue(messageID, out var callback))
|
||||||
|
{
|
||||||
|
callback.Invoke(new MessageEventArgs { playerId = 0, data = data });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### RemoteEditorConnection(运行时模拟模式)
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
internal class RemoteEditorConnection
|
||||||
|
{
|
||||||
|
private static RemoteEditorConnection _instance;
|
||||||
|
private readonly Dictionary<Guid, UnityAction<MessageEventArgs>> _messageCallbacks;
|
||||||
|
|
||||||
|
public static RemoteEditorConnection Instance
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_instance == null)
|
||||||
|
_instance = new RemoteEditorConnection();
|
||||||
|
return _instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Register(Guid messageID, UnityAction<MessageEventArgs> callback)
|
||||||
|
{
|
||||||
|
_messageCallbacks.Add(messageID, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Send(Guid messageID, byte[] data)
|
||||||
|
{
|
||||||
|
// 发送给虚拟运行时连接
|
||||||
|
RemotePlayerConnection.Instance.HandleEditorMessage(messageID, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void HandlePlayerMessage(Guid messageID, byte[] data)
|
||||||
|
{
|
||||||
|
if (_messageCallbacks.TryGetValue(messageID, out var callback))
|
||||||
|
{
|
||||||
|
callback.Invoke(new MessageEventArgs { playerId = 0, data = data });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 通信协议
|
||||||
|
|
||||||
|
### 协议规范
|
||||||
|
|
||||||
|
**协议版本:** 2.3.3
|
||||||
|
|
||||||
|
**编码格式:**
|
||||||
|
```
|
||||||
|
C# 对象 → JsonUtility.ToJson() → JSON 字符串 → Encoding.UTF8.GetBytes() → byte[]
|
||||||
|
```
|
||||||
|
|
||||||
|
**消息类型:**
|
||||||
|
|
||||||
|
| 方向 | GUID | 数据类型 | 说明 |
|
||||||
|
|------|------|----------|------|
|
||||||
|
| 编辑器→运行时 | `4d1926c9df5b052469a1c63448b7609a` | `RemoteCommand` | 采样命令 |
|
||||||
|
| 运行时→编辑器 | `e34a5702dd353724aa315fb8011f08c3` | `DebugReport` | 调试报告 |
|
||||||
|
|
||||||
|
### 双向通信流程
|
||||||
|
|
||||||
|
```
|
||||||
|
[编辑器 UI]
|
||||||
|
│
|
||||||
|
├─ 用户点击 "Sample" 按钮
|
||||||
|
│ └─ 发送 RemoteCommand (SampleOnce)
|
||||||
|
│
|
||||||
|
└─ 用户开启 "Record" 开关
|
||||||
|
└─ 发送 RemoteCommand (SampleAuto, "open")
|
||||||
|
|
||||||
|
↓ RemoteEditorConnection.Send()
|
||||||
|
↓
|
||||||
|
RemotePlayerConnection.HandleEditorMessage()
|
||||||
|
↓ 触发回调
|
||||||
|
|
||||||
|
[运行时]
|
||||||
|
RemoteDebuggerInRuntime.OnHandleEditorMessage()
|
||||||
|
↓
|
||||||
|
设置采样标志 (_sampleOnce 或 _autoSample)
|
||||||
|
↓
|
||||||
|
LateUpdate 中采样
|
||||||
|
↓
|
||||||
|
YooAssets.GetDebugReport()
|
||||||
|
├─ 收集所有 ResourcePackage 数据
|
||||||
|
├─ DebugProviderInfo[] (从 ProviderDic)
|
||||||
|
├─ DebugBundleInfo[] (从 LoaderDic)
|
||||||
|
└─ DebugOperationInfo[] (从 _operations)
|
||||||
|
↓
|
||||||
|
DebugReport.Serialize()
|
||||||
|
↓
|
||||||
|
RemotePlayerConnection.Send()
|
||||||
|
↓
|
||||||
|
RemoteEditorConnection.HandlePlayerMessage()
|
||||||
|
↓
|
||||||
|
|
||||||
|
[编辑器]
|
||||||
|
AssetBundleDebuggerWindow.OnHandlePlayerMessage()
|
||||||
|
↓
|
||||||
|
版本校验 (DebuggerVersion)
|
||||||
|
↓
|
||||||
|
RemotePlayerSession.AddDebugReport()
|
||||||
|
↓
|
||||||
|
缓存到历史记录 (最多 500 帧)
|
||||||
|
↓
|
||||||
|
UI 刷新显示
|
||||||
|
```
|
||||||
|
|
||||||
|
### 版本校验机制
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 编辑器端版本校验
|
||||||
|
private void OnHandlePlayerMessage(MessageEventArgs args)
|
||||||
|
{
|
||||||
|
var debugReport = DebugReport.Deserialize(args.data);
|
||||||
|
|
||||||
|
// 版本校验
|
||||||
|
if (debugReport.DebuggerVersion != RemoteDebuggerDefine.DebuggerVersion)
|
||||||
|
{
|
||||||
|
Debug.LogWarning(
|
||||||
|
$"Debugger versions are inconsistent : " +
|
||||||
|
$"{debugReport.DebuggerVersion} != {RemoteDebuggerDefine.DebuggerVersion}");
|
||||||
|
return; // 丢弃不兼容的数据
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理数据...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**设计意图:** 防止编辑器和运行时的调试器版本不一致导致的数据格式错误。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 数据收集流程
|
||||||
|
|
||||||
|
### 完整采样流程
|
||||||
|
|
||||||
|
```
|
||||||
|
RemoteDebuggerInRuntime.LateUpdate()
|
||||||
|
↓
|
||||||
|
检查 _sampleOnce 或 _autoSample 标志
|
||||||
|
↓ YES
|
||||||
|
调用 YooAssets.GetDebugReport()
|
||||||
|
│
|
||||||
|
├─ 初始化 DebugReport
|
||||||
|
├─ 设置 FrameCount = Time.frameCount
|
||||||
|
├─ 遍历每个 ResourcePackage:
|
||||||
|
│ │
|
||||||
|
│ └─ package.GetDebugPackageData()
|
||||||
|
│ │
|
||||||
|
│ ├─ 创建 DebugPackageData
|
||||||
|
│ ├─ 设置 PackageName
|
||||||
|
│ │
|
||||||
|
│ ├─ 收集 ProviderInfos:
|
||||||
|
│ │ ResourceManager.GetDebugProviderInfos()
|
||||||
|
│ │ 遍历 ProviderDic:
|
||||||
|
│ │ └─ 每个 ProviderOperation 提供:
|
||||||
|
│ │ - MainAssetInfo.AssetPath
|
||||||
|
│ │ - SpawnScene(场景名)
|
||||||
|
│ │ - BeginTime(开始时间)
|
||||||
|
│ │ - ProcessTime(耗时)
|
||||||
|
│ │ - RefCount(引用计数)
|
||||||
|
│ │ - Status(加载状态)
|
||||||
|
│ │ - GetDebugDependBundles()(依赖列表)
|
||||||
|
│ │
|
||||||
|
│ ├─ 收集 BundleInfos:
|
||||||
|
│ │ ResourceManager.GetDebugBundleInfos()
|
||||||
|
│ │ 遍历 LoaderDic:
|
||||||
|
│ │ └─ 每个 LoadBundleFileOperation 提供:
|
||||||
|
│ │ - BundleName
|
||||||
|
│ │ - RefCount
|
||||||
|
│ │ - Status
|
||||||
|
│ │ - FilterReferenceBundles()(反向依赖)
|
||||||
|
│ │
|
||||||
|
│ └─ 收集 OperationInfos:
|
||||||
|
│ OperationSystem.GetDebugOperationInfos(PackageName)
|
||||||
|
│ 遍历 _operations(按 PackageName 过滤):
|
||||||
|
│ └─ 递归构建操作树:
|
||||||
|
│ - GetType().Name(操作类名)
|
||||||
|
│ - GetOperationDesc()(自定义描述)
|
||||||
|
│ - Priority(优先级)
|
||||||
|
│ - Progress(进度)
|
||||||
|
│ - BeginTime(开始时间)
|
||||||
|
│ - ProcessTime(耗时)
|
||||||
|
│ - Status(状态)
|
||||||
|
│ - Childs(子操作列表)
|
||||||
|
│
|
||||||
|
└─ 返回 DebugReport
|
||||||
|
```
|
||||||
|
|
||||||
|
### 性能指标收集
|
||||||
|
|
||||||
|
#### 资源加载耗时
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// AsyncOperationBase 中的自动计时
|
||||||
|
private Stopwatch _watch = null;
|
||||||
|
|
||||||
|
internal void InternalStart()
|
||||||
|
{
|
||||||
|
if (_watch == null)
|
||||||
|
{
|
||||||
|
BeginTime = SpawnTimeToString(UnityEngine.Time.realtimeSinceStartup);
|
||||||
|
_watch = Stopwatch.StartNew();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void InternalUpdate()
|
||||||
|
{
|
||||||
|
ProcessTime = _watch.ElapsedMilliseconds;
|
||||||
|
// ... 持续计时
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 场景信息追踪
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// ProviderOperation.cs
|
||||||
|
[Conditional("DEBUG")] // 仅在 DEBUG 模式下启用
|
||||||
|
public void InitProviderDebugInfo()
|
||||||
|
{
|
||||||
|
SpawnScene = UnityEngine.SceneManagement.SceneManager.GetActiveScene().name;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 引用计数监控
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// ProviderOperation 和 LoadBundleFileOperation 都维护 RefCount
|
||||||
|
public int RefCount { get; } // 当前被引用的次数
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 与其他模块的交互
|
||||||
|
|
||||||
|
```
|
||||||
|
YooAssets (全局入口)
|
||||||
|
│
|
||||||
|
├─ 初始化阶段:
|
||||||
|
│ #if DEBUG
|
||||||
|
│ _driver.AddComponent<RemoteDebuggerInRuntime>();
|
||||||
|
│ #endif
|
||||||
|
│
|
||||||
|
└─ 数据收集入口:
|
||||||
|
GetDebugReport()
|
||||||
|
├─ 遍历所有 ResourcePackage
|
||||||
|
└─ 构建 DebugReport
|
||||||
|
|
||||||
|
ResourcePackage (资源包)
|
||||||
|
│
|
||||||
|
└─ GetDebugPackageData()
|
||||||
|
├─ 调用 ResourceManager.GetDebugProviderInfos()
|
||||||
|
├─ 调用 ResourceManager.GetDebugBundleInfos()
|
||||||
|
└─ 调用 OperationSystem.GetDebugOperationInfos()
|
||||||
|
|
||||||
|
ResourceManager (资源管理器)
|
||||||
|
│
|
||||||
|
├─ GetDebugProviderInfos()
|
||||||
|
│ └─ 遍历 ProviderDic (Dictionary<string, ProviderOperation>)
|
||||||
|
│
|
||||||
|
└─ GetDebugBundleInfos()
|
||||||
|
└─ 遍历 LoaderDic (Dictionary<string, LoadBundleFileOperation>)
|
||||||
|
|
||||||
|
OperationSystem (操作系统)
|
||||||
|
│
|
||||||
|
└─ GetDebugOperationInfos(packageName)
|
||||||
|
└─ 遍历 _operations (List<AsyncOperationBase>)
|
||||||
|
└─ 递归收集子操作 (Childs)
|
||||||
|
|
||||||
|
AsyncOperationBase (异步操作基类)
|
||||||
|
│
|
||||||
|
├─ BeginTime:操作开始时间
|
||||||
|
├─ ProcessTime:累计处理耗时
|
||||||
|
├─ Status:操作状态
|
||||||
|
├─ Progress:进度
|
||||||
|
└─ GetOperationDesc():自定义描述
|
||||||
|
|
||||||
|
ProviderOperation (资源提供者)
|
||||||
|
│
|
||||||
|
├─ SpawnScene:加载时的活跃场景
|
||||||
|
└─ GetDebugDependBundles():依赖包列表
|
||||||
|
|
||||||
|
LoadBundleFileOperation (Bundle 加载器)
|
||||||
|
│
|
||||||
|
├─ RefCount:引用计数
|
||||||
|
└─ LoadBundleInfo:Bundle 信息
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 使用场景
|
||||||
|
|
||||||
|
### 场景 1:运行时资源泄漏诊断
|
||||||
|
|
||||||
|
**问题:** 游戏切换场景后内存持续增长,怀疑有资源未释放。
|
||||||
|
|
||||||
|
**诊断步骤:**
|
||||||
|
1. 打开 AssetBundle Debugger 窗口
|
||||||
|
2. 开启 Record 模式(自动采样)
|
||||||
|
3. 切换场景前后观察 ProviderInfos 列表
|
||||||
|
4. 检查 `RefCount > 0` 且 `SpawnScene` 为旧场景的资源
|
||||||
|
5. 定位未释放的资源和对应的代码位置
|
||||||
|
|
||||||
|
**关键字段:**
|
||||||
|
- `SpawnScene`:资源在哪个场景被加载
|
||||||
|
- `RefCount`:引用计数,应该为 0
|
||||||
|
- `AssetPath`:资源路径,定位具体资源
|
||||||
|
|
||||||
|
### 场景 2:资源加载性能分析
|
||||||
|
|
||||||
|
**问题:** 首次加载场景卡顿严重。
|
||||||
|
|
||||||
|
**诊断步骤:**
|
||||||
|
1. 单次采样(Sample Once)
|
||||||
|
2. 切换到 Asset View
|
||||||
|
3. 按 `LoadingTime` 降序排序
|
||||||
|
4. 识别加载耗时最长的资源
|
||||||
|
5. 分析 `DependBundles` 了解依赖链
|
||||||
|
|
||||||
|
**关键字段:**
|
||||||
|
- `LoadingTime`:加载耗时(毫秒)
|
||||||
|
- `DependBundles`:依赖的 Bundle 列表
|
||||||
|
- `Status`:加载状态
|
||||||
|
|
||||||
|
### 场景 3:Bundle 引用分析
|
||||||
|
|
||||||
|
**问题:** 某个 Bundle 无法被卸载。
|
||||||
|
|
||||||
|
**诊断步骤:**
|
||||||
|
1. 切换到 Bundle View
|
||||||
|
2. 搜索目标 Bundle
|
||||||
|
3. 检查 `RefCount` 和 `ReferenceBundles`
|
||||||
|
4. 追踪哪些资源正在使用该 Bundle
|
||||||
|
5. 定位未释放的资源引用
|
||||||
|
|
||||||
|
**关键字段:**
|
||||||
|
- `RefCount`:Bundle 引用计数
|
||||||
|
- `ReferenceBundles`:反向依赖列表
|
||||||
|
- `Status`:Bundle 加载状态
|
||||||
|
|
||||||
|
### 场景 4:异步操作监控
|
||||||
|
|
||||||
|
**问题:** 复杂的初始化流程卡住,不知道在哪个步骤。
|
||||||
|
|
||||||
|
**诊断步骤:**
|
||||||
|
1. 切换到 Operation View
|
||||||
|
2. 查看操作树结构
|
||||||
|
3. 检查 `Status` 为 `Processing` 的操作
|
||||||
|
4. 分析 `Progress` 了解进度
|
||||||
|
5. 通过 `Childs` 了解操作依赖关系
|
||||||
|
|
||||||
|
**关键字段:**
|
||||||
|
- `OperationName`:操作类型
|
||||||
|
- `OperationDesc`:操作描述
|
||||||
|
- `Progress`:进度(0.0 - 1.0)
|
||||||
|
- `Childs`:子操作列表
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 数据导出
|
||||||
|
|
||||||
|
### JSON 导出功能
|
||||||
|
|
||||||
|
编辑器窗口支持导出当前帧的完整调试数据为 JSON 文件。
|
||||||
|
|
||||||
|
**导出示例:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"DebuggerVersion": "2.3.3",
|
||||||
|
"FrameCount": 1234,
|
||||||
|
"PackageDatas": [
|
||||||
|
{
|
||||||
|
"PackageName": "DefaultPackage",
|
||||||
|
"ProviderInfos": [
|
||||||
|
{
|
||||||
|
"PackageName": "DefaultPackage",
|
||||||
|
"AssetPath": "Assets/Prefabs/Player.prefab",
|
||||||
|
"SpawnScene": "GameScene",
|
||||||
|
"BeginTime": "12:34:56.789",
|
||||||
|
"LoadingTime": 45,
|
||||||
|
"RefCount": 1,
|
||||||
|
"Status": "Succeed",
|
||||||
|
"DependBundles": [
|
||||||
|
"assets_prefabs.bundle"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"BundleInfos": [
|
||||||
|
{
|
||||||
|
"BundleName": "assets_prefabs.bundle",
|
||||||
|
"RefCount": 1,
|
||||||
|
"Status": "Succeed",
|
||||||
|
"ReferenceBundles": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"OperationInfos": [
|
||||||
|
{
|
||||||
|
"OperationName": "LoadAssetOperation",
|
||||||
|
"OperationDesc": "Load assets_prefabs.bundle",
|
||||||
|
"Priority": 0,
|
||||||
|
"Progress": 1.0,
|
||||||
|
"BeginTime": "12:34:56.745",
|
||||||
|
"ProcessTime": 44,
|
||||||
|
"Status": "Succeed",
|
||||||
|
"Childs": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**用途:**
|
||||||
|
- 离线分析和归档
|
||||||
|
- 性能数据对比
|
||||||
|
- 问题复现和追踪
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 系统架构图
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Unity Editor Window │
|
||||||
|
│ AssetBundleDebuggerWindow │
|
||||||
|
│ ┌──────────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ UI Controls: │ │
|
||||||
|
│ │ - Sample Button (SampleOnce) │ │
|
||||||
|
│ │ - Record Toggle (SampleAuto) │ │
|
||||||
|
│ │ - View Mode Menu (Asset/Bundle/Operation View) │ │
|
||||||
|
│ │ - Frame Slider (历史帧导航) │ │
|
||||||
|
│ │ - Search Field (关键词搜索) │ │
|
||||||
|
│ │ - Export Button (JSON 导出) │ │
|
||||||
|
│ └──────────────────────────────────────────────────────────────┘ │
|
||||||
|
└────────────┬──────────────────────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
┌─────▼───────────────────────────────────┐
|
||||||
|
│ RemoteEditorConnection (虚拟连接) │
|
||||||
|
│ - Register callbacks │
|
||||||
|
│ - Send/Receive commands & reports │
|
||||||
|
└─────┬──────────────────────────────┬────┘
|
||||||
|
│ │
|
||||||
|
┌────────▼─────────────┐ ┌──────────▼───────────────┐
|
||||||
|
│ RemoteCommand │ │ DebugReport │
|
||||||
|
│ (Serialize) │ │ (Deserialize) │
|
||||||
|
│ ↓ JSON │ │ ← JSON │
|
||||||
|
│ ↓ UTF-8 bytes │ │ ← UTF-8 bytes │
|
||||||
|
└────────┬─────────────┘ └──────────┬───────────────┘
|
||||||
|
│ │
|
||||||
|
│ ═══════════════════════ │
|
||||||
|
│ Internet / Emulation │
|
||||||
|
│ ═══════════════════════ │
|
||||||
|
│ │
|
||||||
|
┌────────▼─────────────┐ ┌──────────▼───────────────┐
|
||||||
|
│ PlayerConnection │ │ RemotePlayerConnection │
|
||||||
|
│ (真实连接) │ │ (虚拟连接) │
|
||||||
|
│ 或模拟连接 │ │ │
|
||||||
|
└────────┬─────────────┘ └──────────┬───────────────┘
|
||||||
|
│ │
|
||||||
|
└──────────────┬───────────────┘
|
||||||
|
│
|
||||||
|
┌──────▼──────────────────────────────┐
|
||||||
|
│ RemoteDebuggerInRuntime │
|
||||||
|
│ (MonoBehaviour) │
|
||||||
|
│ ┌──────────────────────────────┐ │
|
||||||
|
│ │ _sampleOnce (bool) │ │
|
||||||
|
│ │ _autoSample (bool) │ │
|
||||||
|
│ └──────────────────────────────┘ │
|
||||||
|
│ ┌──────────────────────────────┐ │
|
||||||
|
│ │ Awake() - 初始化连接 │ │
|
||||||
|
│ │ OnEnable() - 注册回调 │ │
|
||||||
|
│ │ LateUpdate() - 采样触发 │ │
|
||||||
|
│ │ OnHandleEditorMessage() - 收命令│ │
|
||||||
|
│ └──────────────────────────────┘ │
|
||||||
|
└──────────┬──────────────────────────┘
|
||||||
|
│
|
||||||
|
┌──────▼────────────────────┐
|
||||||
|
│ YooAssets.GetDebugReport() │
|
||||||
|
│ (全系统数据收集入口) │
|
||||||
|
└──────┬────────────────────┘
|
||||||
|
│
|
||||||
|
┌───────────┼───────────┐
|
||||||
|
│ │ │
|
||||||
|
┌──────────▼──┐ ┌─────▼────┐ ┌──▼─────────────┐
|
||||||
|
│ResourcePkg 1│ │ResourcePkg2 │ResourcePackageN│
|
||||||
|
└──────┬──────┘ └────┬─────┘ └──┬──────────┘
|
||||||
|
│ │ │
|
||||||
|
└───────────────┼──────────┘
|
||||||
|
│
|
||||||
|
┌───────────▼──────────┐
|
||||||
|
│ DebugPackageData │
|
||||||
|
│ ┌─────────────────┐ │
|
||||||
|
│ │ PackageName │ │
|
||||||
|
│ │ ProviderInfos[] │ │
|
||||||
|
│ │ BundleInfos[] │ │
|
||||||
|
│ │ OperationInfos[]│ │
|
||||||
|
│ └─────────────────┘ │
|
||||||
|
└─────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **DEBUG 模式自动启用**
|
||||||
|
- 诊断系统仅在 `DEBUG` 模式下启用(通过 `#if DEBUG` 条件编译)
|
||||||
|
- Release 构建中不会包含诊断代码,无性能开销
|
||||||
|
|
||||||
|
2. **版本兼容性**
|
||||||
|
- 编辑器和运行时的调试器版本必须一致
|
||||||
|
- 版本不一致的数据会被自动丢弃
|
||||||
|
|
||||||
|
3. **历史数据限制**
|
||||||
|
- 最多缓存 500 帧历史数据(可配置)
|
||||||
|
- 超过限制后,最早的数据会被移除
|
||||||
|
|
||||||
|
4. **JSON 序列化深度限制**
|
||||||
|
- Unity JsonUtility 序列化深度限制为 10 层
|
||||||
|
- 操作树(Childs)嵌套过深可能导致序列化失败
|
||||||
|
|
||||||
|
5. **性能开销**
|
||||||
|
- 单次采样:低开销,仅在需要时采集
|
||||||
|
- 自动采样:每帧采集,有一定性能开销,建议仅在需要时开启
|
||||||
|
|
||||||
|
6. **LateUpdate 时机**
|
||||||
|
- 采样在 LateUpdate 中执行,确保该帧所有操作已更新
|
||||||
|
- 避免在采样过程中资源状态发生变化
|
||||||
|
|
||||||
|
7. **非序列化字典**
|
||||||
|
- `DebugPackageData.BundleInfoDic` 使用 `[NonSerialized]` 标记
|
||||||
|
- 字典在首次查询时才构建,减少序列化开销
|
||||||
|
|
||||||
|
8. **场景追踪条件编译**
|
||||||
|
- `SpawnScene` 字段仅在 DEBUG 模式下赋值(`[Conditional("DEBUG")]`)
|
||||||
|
- Release 构建中该字段为空字符串
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 性能优化建议
|
||||||
|
|
||||||
|
1. **按需采样**
|
||||||
|
- 优先使用单次采样(Sample Once)
|
||||||
|
- 仅在需要连续监控时开启自动采样(Record)
|
||||||
|
|
||||||
|
2. **及时关闭 Record**
|
||||||
|
- 分析完成后及时关闭自动采样
|
||||||
|
- 避免不必要的性能开销
|
||||||
|
|
||||||
|
3. **合理设置历史缓存**
|
||||||
|
- 根据内存情况调整 `MaxReportCount`
|
||||||
|
- 默认 500 帧已足够大多数分析场景
|
||||||
|
|
||||||
|
4. **导出数据离线分析**
|
||||||
|
- 对于复杂的性能问题,导出 JSON 数据
|
||||||
|
- 在编辑器外使用专业工具进行分析
|
||||||
|
|
||||||
|
5. **Release 构建移除诊断代码**
|
||||||
|
- 确保 Release 构建使用 Release 配置
|
||||||
|
- 诊断代码通过 `#if DEBUG` 自动移除
|
||||||
7
Assets/YooAsset/Runtime/DiagnosticSystem/README.md.meta
Normal file
7
Assets/YooAsset/Runtime/DiagnosticSystem/README.md.meta
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 5fa2b66c20800124c8dd5cb77e854ce3
|
||||||
|
TextScriptImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: a8f060786f8775b4a82cc3f55d9135e2
|
guid: bb70e2274ce5a5d419cfbd7212efdf4a
|
||||||
folderAsset: yes
|
folderAsset: yes
|
||||||
DefaultImporter:
|
DefaultImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
using System;
|
||||||
|
using UnityEngine.Networking;
|
||||||
|
|
||||||
|
namespace YooAsset
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// UnityWebRequest 下载后台
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 基于 Unity 内置 UnityWebRequest 实现的下载后台。
|
||||||
|
/// 支持自定义 UnityWebRequest 创建方式,例如添加证书验证、代理设置等。
|
||||||
|
/// </remarks>
|
||||||
|
internal sealed class UnityWebRequestBackend : IDownloadBackend
|
||||||
|
{
|
||||||
|
private readonly UnityWebRequestCreator _webRequestCreator;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 后端名称
|
||||||
|
/// </summary>
|
||||||
|
public string Name
|
||||||
|
{
|
||||||
|
get { return nameof(UnityWebRequestBackend); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建 UnityWebRequest 下载后端(使用默认创建方式)
|
||||||
|
/// </summary>
|
||||||
|
public UnityWebRequestBackend() : this(null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建 UnityWebRequest 下载后端
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="webRequestCreator">
|
||||||
|
/// 自定义 UnityWebRequest 创建委托(可选)。
|
||||||
|
/// 如果为 null,则使用默认的 UnityWebRequest 构造方式。
|
||||||
|
/// </param>
|
||||||
|
public UnityWebRequestBackend(UnityWebRequestCreator webRequestCreator)
|
||||||
|
{
|
||||||
|
_webRequestCreator = webRequestCreator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 驱动更新
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// UnityWebRequest 由 Unity 引擎自动驱动,无需额外更新。
|
||||||
|
/// </remarks>
|
||||||
|
public void Update()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 释放资源
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
// 无需释放资源
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建 HEAD 请求
|
||||||
|
/// </summary>
|
||||||
|
public IDownloadHeadRequest CreateHeadRequest(DownloadDataRequestArgs args)
|
||||||
|
{
|
||||||
|
return new UnityWebRequestHeadDownloader(args, _webRequestCreator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建文件下载请求
|
||||||
|
/// </summary>
|
||||||
|
public IDownloadFileRequest CreateFileRequest(DownloadFileRequestArgs args)
|
||||||
|
{
|
||||||
|
return new UnityWebRequestFileDownloader(args, _webRequestCreator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建字节下载请求
|
||||||
|
/// </summary>
|
||||||
|
public IDownloadBytesRequest CreateBytesRequest(DownloadDataRequestArgs args)
|
||||||
|
{
|
||||||
|
return new UnityWebRequestBytesDownloader(args, _webRequestCreator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建文本下载请求
|
||||||
|
/// </summary>
|
||||||
|
public IDownloadTextRequest CreateTextRequest(DownloadDataRequestArgs args)
|
||||||
|
{
|
||||||
|
return new UnityWebRequestTextDownloader(args, _webRequestCreator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建 AssetBundle 下载请求
|
||||||
|
/// </summary>
|
||||||
|
public IDownloadAssetBundleRequest CreateAssetBundleRequest(DownloadAssetBundleRequestArgs args)
|
||||||
|
{
|
||||||
|
return new UnityWebRequestAssetBundleDownloader(args, _webRequestCreator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建模拟下载请求
|
||||||
|
/// </summary>
|
||||||
|
public IDownloadFileRequest CreateSimulateRequest(DownloadSimulateRequestArgs args)
|
||||||
|
{
|
||||||
|
return new VirtualFileDownloader(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 932e4c4574244dac8c27d9ddc156f8ff
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
using UnityEngine.Networking;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace YooAsset
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 自定义下载器的请求委托
|
||||||
|
/// </summary>
|
||||||
|
public delegate UnityWebRequest UnityWebRequestCreator(string url, string method);
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 0fa3e6346decc4d4db3b03e6ae93e57a
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 1d30af9086de4fb282c2cdd3d26244c0
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
using System;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.Networking;
|
||||||
|
|
||||||
|
namespace YooAsset
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// UnityWebRequest AssetBundle 下载器
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 下载并加载 Unity AssetBundle 资源包。
|
||||||
|
/// 支持 Unity 内置缓存机制和 CRC 校验。
|
||||||
|
/// </remarks>
|
||||||
|
internal sealed class UnityWebRequestAssetBundleDownloader : UnityWebRequestDownloaderBase, IDownloadAssetBundleRequest
|
||||||
|
{
|
||||||
|
private readonly DownloadAssetBundleRequestArgs _args;
|
||||||
|
private DownloadHandlerAssetBundle _downloadHandler;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 下载结果(AssetBundle 对象)
|
||||||
|
/// </summary>
|
||||||
|
public AssetBundle Result { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构造 AssetBundle 下载器
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="args">AssetBundle 下载参数</param>
|
||||||
|
/// <param name="webRequestCreator">UnityWebRequest 创建器(可选)</param>
|
||||||
|
public UnityWebRequestAssetBundleDownloader(DownloadAssetBundleRequestArgs args, UnityWebRequestCreator webRequestCreator)
|
||||||
|
: base(args.URL, webRequestCreator)
|
||||||
|
{
|
||||||
|
_args = args;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建 UnityWebRequest
|
||||||
|
/// </summary>
|
||||||
|
protected override void CreateWebRequest()
|
||||||
|
{
|
||||||
|
_downloadHandler = CreateAssetBundleDownloadHandler();
|
||||||
|
_webRequest = CreateUnityWebRequestGet(URL);
|
||||||
|
_webRequest.downloadHandler = _downloadHandler;
|
||||||
|
_webRequest.disposeDownloadHandlerOnDispose = true;
|
||||||
|
ApplyRequestOptions(_args.Timeout, _args.WatchdogTime, _args.Headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 请求成功时的回调
|
||||||
|
/// </summary>
|
||||||
|
protected override void OnRequestSucceed()
|
||||||
|
{
|
||||||
|
AssetBundle assetBundle = _downloadHandler.assetBundle;
|
||||||
|
if (assetBundle == null)
|
||||||
|
{
|
||||||
|
Status = EDownloadRequestStatus.Failed;
|
||||||
|
Error = $"[{GetType().Name}] URL: {URL} - AssetBundle object is null";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Result = assetBundle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建 AssetBundle 下载处理器
|
||||||
|
/// </summary>
|
||||||
|
private DownloadHandlerAssetBundle CreateAssetBundleDownloadHandler()
|
||||||
|
{
|
||||||
|
DownloadHandlerAssetBundle handler;
|
||||||
|
|
||||||
|
if (_args.DisableUnityWebCache)
|
||||||
|
{
|
||||||
|
// 禁用 Unity 缓存
|
||||||
|
handler = new DownloadHandlerAssetBundle(URL, _args.UnityCRC);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(_args.FileHash))
|
||||||
|
throw new YooInternalException("File hash is null or empty !");
|
||||||
|
|
||||||
|
// 使用 Unity 缓存
|
||||||
|
// 说明:The file hash defining the version of the asset bundle.
|
||||||
|
Hash128 fileHash = Hash128.Parse(_args.FileHash);
|
||||||
|
handler = new DownloadHandlerAssetBundle(URL, fileHash, _args.UnityCRC);
|
||||||
|
}
|
||||||
|
|
||||||
|
return handler;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: e01cc308d7179a34281087fafe455b42
|
guid: 6f635344c08e2b04295a108c2bcc6a40
|
||||||
MonoImporter:
|
MonoImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
using System;
|
||||||
|
using UnityEngine.Networking;
|
||||||
|
|
||||||
|
namespace YooAsset
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// UnityWebRequest 字节下载器
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 将下载内容保存到内存中的字节数组。
|
||||||
|
/// </remarks>
|
||||||
|
internal sealed class UnityWebRequestBytesDownloader : UnityWebRequestDownloaderBase, IDownloadBytesRequest
|
||||||
|
{
|
||||||
|
private readonly DownloadDataRequestArgs _args;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 下载结果(字节数组)
|
||||||
|
/// </summary>
|
||||||
|
public byte[] Result { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构造字节数组下载器
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="args">数据下载参数</param>
|
||||||
|
/// <param name="webRequestCreator">UnityWebRequest 创建器(可选)</param>
|
||||||
|
public UnityWebRequestBytesDownloader(DownloadDataRequestArgs args, UnityWebRequestCreator webRequestCreator)
|
||||||
|
: base(args.URL, webRequestCreator)
|
||||||
|
{
|
||||||
|
_args = args;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建 UnityWebRequest
|
||||||
|
/// </summary>
|
||||||
|
protected override void CreateWebRequest()
|
||||||
|
{
|
||||||
|
var handler = new DownloadHandlerBuffer();
|
||||||
|
_webRequest = CreateUnityWebRequestGet(URL);
|
||||||
|
_webRequest.downloadHandler = handler;
|
||||||
|
_webRequest.disposeDownloadHandlerOnDispose = true;
|
||||||
|
ApplyRequestOptions(_args.Timeout, _args.WatchdogTime, _args.Headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 请求成功时的回调
|
||||||
|
/// </summary>
|
||||||
|
protected override void OnRequestSucceed()
|
||||||
|
{
|
||||||
|
Result = _webRequest.downloadHandler.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 6602c4be2ef295546b7bbb328de8fb0c
|
guid: 38884a96e8c2df74082d4e059e2e73ed
|
||||||
MonoImporter:
|
MonoImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
@@ -0,0 +1,293 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine.Networking;
|
||||||
|
|
||||||
|
namespace YooAsset
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// UnityWebRequest 下载器基类
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 封装 UnityWebRequest 的通用下载逻辑,包括状态管理、进度追踪等。
|
||||||
|
/// 子类只需实现 CreateWebRequest 方法来创建特定类型的下载请求。
|
||||||
|
/// </remarks>
|
||||||
|
internal abstract class UnityWebRequestDownloaderBase : IDownloadRequest
|
||||||
|
{
|
||||||
|
private readonly UnityWebRequestCreator _webRequestCreator;
|
||||||
|
protected UnityWebRequest _webRequest;
|
||||||
|
|
||||||
|
// 看门狗相关
|
||||||
|
private int _watchdogTime = 0;
|
||||||
|
private bool _watchdogAborted = false;
|
||||||
|
private long _lastDownloadBytes = -1;
|
||||||
|
private double _lastGetDataTime;
|
||||||
|
|
||||||
|
#region 接口实现
|
||||||
|
/// <summary>
|
||||||
|
/// 请求地址
|
||||||
|
/// </summary>
|
||||||
|
public string URL { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否完成
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 每次调用都会主动轮询请求 PollingRequest
|
||||||
|
/// </remarks>
|
||||||
|
public bool IsDone
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
PollingRequest();
|
||||||
|
return Status == EDownloadRequestStatus.Succeed
|
||||||
|
|| Status == EDownloadRequestStatus.Failed
|
||||||
|
|| Status == EDownloadRequestStatus.Aborted;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 请求状态
|
||||||
|
/// </summary>
|
||||||
|
public EDownloadRequestStatus Status { get; protected set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前下载进度(0f - 1f)
|
||||||
|
/// </summary>
|
||||||
|
public float DownloadProgress { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前请求已接收的字节数
|
||||||
|
/// </summary>
|
||||||
|
public long DownloadedBytes { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// HTTP 返回码
|
||||||
|
/// </summary>
|
||||||
|
public long HttpCode { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 错误信息
|
||||||
|
/// </summary>
|
||||||
|
public string Error { get; protected set; }
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构造下载器基类
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="url">请求地址</param>
|
||||||
|
/// <param name="webRequestCreator">UnityWebRequest 创建器(可选)</param>
|
||||||
|
protected UnityWebRequestDownloaderBase(string url, UnityWebRequestCreator webRequestCreator)
|
||||||
|
{
|
||||||
|
URL = url;
|
||||||
|
_webRequestCreator = webRequestCreator;
|
||||||
|
Status = EDownloadRequestStatus.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发起请求
|
||||||
|
/// </summary>
|
||||||
|
public void SendRequest()
|
||||||
|
{
|
||||||
|
if (Status == EDownloadRequestStatus.None)
|
||||||
|
{
|
||||||
|
Status = EDownloadRequestStatus.Running;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
CreateWebRequest();
|
||||||
|
|
||||||
|
if (_webRequest == null)
|
||||||
|
{
|
||||||
|
Status = EDownloadRequestStatus.Failed;
|
||||||
|
Error = $"[{GetType().Name}] Created web request is null.";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_webRequest.SendWebRequest();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Status = EDownloadRequestStatus.Failed;
|
||||||
|
Error = $"[{GetType().Name}] Failed to create web request : {ex.Message}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 轮询请求
|
||||||
|
/// </summary>
|
||||||
|
public void PollingRequest()
|
||||||
|
{
|
||||||
|
if (Status != EDownloadRequestStatus.Running)
|
||||||
|
return;
|
||||||
|
|
||||||
|
DownloadProgress = _webRequest.downloadProgress;
|
||||||
|
DownloadedBytes = (long)_webRequest.downloadedBytes;
|
||||||
|
|
||||||
|
CheckWatchdog();
|
||||||
|
if (_webRequest.isDone == false)
|
||||||
|
return;
|
||||||
|
|
||||||
|
HttpCode = _webRequest.responseCode;
|
||||||
|
#if UNITY_2020_3_OR_NEWER
|
||||||
|
bool isSuccess = _webRequest.result == UnityWebRequest.Result.Success;
|
||||||
|
#else
|
||||||
|
bool isSuccess = !_webRequest.isNetworkError && !_webRequest.isHttpError;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (isSuccess)
|
||||||
|
{
|
||||||
|
Status = EDownloadRequestStatus.Succeed;
|
||||||
|
OnRequestSucceed();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Status = EDownloadRequestStatus.Failed;
|
||||||
|
Error = $"[{GetType().Name}] URL: {URL} - 错误: {_webRequest.error}";
|
||||||
|
OnRequestFailed();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 完成后释放
|
||||||
|
DisposeWebRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 中止请求
|
||||||
|
/// </summary>
|
||||||
|
public void AbortRequest()
|
||||||
|
{
|
||||||
|
if (Status == EDownloadRequestStatus.None || Status == EDownloadRequestStatus.Running)
|
||||||
|
{
|
||||||
|
Status = EDownloadRequestStatus.Aborted;
|
||||||
|
if (_webRequest != null)
|
||||||
|
_webRequest.Abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 释放资源
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
DisposeWebRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建 UnityWebRequest(子类实现)
|
||||||
|
/// </summary>
|
||||||
|
protected abstract void CreateWebRequest();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 请求成功时的回调(子类可重写)
|
||||||
|
/// </summary>
|
||||||
|
protected virtual void OnRequestSucceed()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 请求失败时的回调(子类可重写)
|
||||||
|
/// </summary>
|
||||||
|
protected virtual void OnRequestFailed()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建 UnityWebRequest GET 请求
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="requestUrl">请求地址</param>
|
||||||
|
/// <returns>UnityWebRequest 实例</returns>
|
||||||
|
protected UnityWebRequest CreateUnityWebRequestGet(string requestUrl)
|
||||||
|
{
|
||||||
|
if (_webRequestCreator != null)
|
||||||
|
return _webRequestCreator.Invoke(requestUrl, UnityWebRequest.kHttpVerbGET);
|
||||||
|
|
||||||
|
return new UnityWebRequest(requestUrl, UnityWebRequest.kHttpVerbGET);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建 UnityWebRequest HEAD 请求
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="requestUrl">请求地址</param>
|
||||||
|
/// <returns>UnityWebRequest 实例</returns>
|
||||||
|
protected UnityWebRequest CreateUnityWebRequestHead(string requestUrl)
|
||||||
|
{
|
||||||
|
if (_webRequestCreator != null)
|
||||||
|
return _webRequestCreator.Invoke(requestUrl, UnityWebRequest.kHttpVerbHEAD);
|
||||||
|
|
||||||
|
return new UnityWebRequest(requestUrl, UnityWebRequest.kHttpVerbHEAD);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 应用通用请求参数
|
||||||
|
/// </summary>
|
||||||
|
protected void ApplyRequestOptions(int timeout, int watchdogTime, Dictionary<string, string> headers)
|
||||||
|
{
|
||||||
|
if (_webRequest == null)
|
||||||
|
throw new YooInternalException("Web request is null !");
|
||||||
|
|
||||||
|
// 设置看门狗超时时间
|
||||||
|
_watchdogTime = watchdogTime;
|
||||||
|
|
||||||
|
// 设置响应的超时时间
|
||||||
|
if (timeout > 0)
|
||||||
|
_webRequest.timeout = timeout;
|
||||||
|
|
||||||
|
// 设置响应头
|
||||||
|
if (headers != null)
|
||||||
|
{
|
||||||
|
foreach (var header in headers)
|
||||||
|
{
|
||||||
|
_webRequest.SetRequestHeader(header.Key, header.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 检测看门狗
|
||||||
|
/// </summary>
|
||||||
|
private void CheckWatchdog()
|
||||||
|
{
|
||||||
|
if (_watchdogTime == 0)
|
||||||
|
return;
|
||||||
|
if (_watchdogAborted)
|
||||||
|
return;
|
||||||
|
|
||||||
|
#if UNITY_2020_3_OR_NEWER
|
||||||
|
double realtimeSinceStartup = UnityEngine.Time.realtimeSinceStartupAsDouble;
|
||||||
|
#else
|
||||||
|
double realtimeSinceStartup = UnityEngine.Time.realtimeSinceStartup;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (DownloadedBytes != _lastDownloadBytes)
|
||||||
|
{
|
||||||
|
_lastDownloadBytes = DownloadedBytes;
|
||||||
|
_lastGetDataTime = realtimeSinceStartup;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
double deltaTime = realtimeSinceStartup - _lastGetDataTime;
|
||||||
|
if (deltaTime > _watchdogTime)
|
||||||
|
{
|
||||||
|
_watchdogAborted = true;
|
||||||
|
AbortRequest(); //看门狗终止网络请求
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 释放资源
|
||||||
|
/// </summary>
|
||||||
|
private void DisposeWebRequest()
|
||||||
|
{
|
||||||
|
if (_webRequest != null)
|
||||||
|
{
|
||||||
|
//注意:引擎底层会自动调用Abort方法
|
||||||
|
_webRequest.Dispose();
|
||||||
|
_webRequest = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 9e71e850eded0da43906cb4f7cb75629
|
guid: 76b0712524ed69542914db8e44fa64fd
|
||||||
MonoImporter:
|
MonoImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
using System;
|
||||||
|
using UnityEngine.Networking;
|
||||||
|
|
||||||
|
namespace YooAsset
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// UnityWebRequest 文件下载器
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 将下载内容保存到本地文件,支持断点续传和追加写入。
|
||||||
|
/// </remarks>
|
||||||
|
internal sealed class UnityWebRequestFileDownloader : UnityWebRequestDownloaderBase, IDownloadFileRequest
|
||||||
|
{
|
||||||
|
private readonly DownloadFileRequestArgs _args;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 文件保存路径
|
||||||
|
/// </summary>
|
||||||
|
public string SavePath
|
||||||
|
{
|
||||||
|
get { return _args.SavePath; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构造文件下载器
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="args">文件下载参数</param>
|
||||||
|
/// <param name="webRequestCreator">UnityWebRequest 创建器(可选)</param>
|
||||||
|
public UnityWebRequestFileDownloader(DownloadFileRequestArgs args, UnityWebRequestCreator webRequestCreator)
|
||||||
|
: base(args.URL, webRequestCreator)
|
||||||
|
{
|
||||||
|
_args = args;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建 UnityWebRequest
|
||||||
|
/// </summary>
|
||||||
|
protected override void CreateWebRequest()
|
||||||
|
{
|
||||||
|
var handler = new DownloadHandlerFile(_args.SavePath, _args.AppendToFile);
|
||||||
|
handler.removeFileOnAbort = _args.RemoveFileOnAbort;
|
||||||
|
|
||||||
|
_webRequest = CreateUnityWebRequestGet(URL);
|
||||||
|
_webRequest.downloadHandler = handler;
|
||||||
|
_webRequest.disposeDownloadHandlerOnDispose = true;
|
||||||
|
|
||||||
|
// 断点续传:设置 Range 请求头
|
||||||
|
if (_args.ResumeFromBytes > 0)
|
||||||
|
{
|
||||||
|
_webRequest.SetRequestHeader("Range", $"bytes={_args.ResumeFromBytes}-");
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplyRequestOptions(_args.Timeout, _args.WatchdogTime, _args.Headers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 375d88bcf5b9a6146adaf98ceb5369f8
|
guid: 101a4be1bb0a85c4d84ecbf3e74f89e4
|
||||||
MonoImporter:
|
MonoImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine.Networking;
|
||||||
|
|
||||||
|
namespace YooAsset
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// UnityWebRequest HEAD 请求下载器
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 仅获取响应头信息,不下载实际内容。
|
||||||
|
/// 用于检查资源是否存在、获取资源大小、检查缓存有效性等场景。
|
||||||
|
/// </remarks>
|
||||||
|
internal sealed class UnityWebRequestHeadDownloader : UnityWebRequestDownloaderBase, IDownloadHeadRequest
|
||||||
|
{
|
||||||
|
// 注意:缓存响应头(因为 WebRequest 释放后无法获取)
|
||||||
|
private Dictionary<string, string> _cachedResponseHeaders;
|
||||||
|
private readonly DownloadDataRequestArgs _args;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取 ETag 响应头
|
||||||
|
/// </summary>
|
||||||
|
public string ETag
|
||||||
|
{
|
||||||
|
get { return GetResponseHeader("ETag"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取 Last-Modified 响应头
|
||||||
|
/// </summary>
|
||||||
|
public string LastModified
|
||||||
|
{
|
||||||
|
get { return GetResponseHeader("Last-Modified"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取 Content-Type 响应头
|
||||||
|
/// </summary>
|
||||||
|
public string ContentType
|
||||||
|
{
|
||||||
|
get { return GetResponseHeader("Content-Type"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取 Content-Length 响应头
|
||||||
|
/// 预期下载的总字节数
|
||||||
|
/// </summary>
|
||||||
|
public long ContentLength
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
string contentLengthStr = GetResponseHeader("Content-Length");
|
||||||
|
if (string.IsNullOrEmpty(contentLengthStr))
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (long.TryParse(contentLengthStr, out long contentLength))
|
||||||
|
{
|
||||||
|
return contentLength;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构造 HEAD 请求下载器
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="args">数据下载参数</param>
|
||||||
|
/// <param name="webRequestCreator">UnityWebRequest 创建器(可选)</param>
|
||||||
|
public UnityWebRequestHeadDownloader(DownloadDataRequestArgs args, UnityWebRequestCreator webRequestCreator)
|
||||||
|
: base(args.URL, webRequestCreator)
|
||||||
|
{
|
||||||
|
_args = args;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取响应头信息
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">响应头名称(不区分大小写)</param>
|
||||||
|
/// <returns>响应头的值,如果不存在或请求未完成则返回 null</returns>
|
||||||
|
public string GetResponseHeader(string name)
|
||||||
|
{
|
||||||
|
if (_cachedResponseHeaders == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// 注意:UnityWebRequest 的响应头 key 是小写的
|
||||||
|
string lowerName = name.ToLowerInvariant();
|
||||||
|
if (_cachedResponseHeaders.TryGetValue(lowerName, out string value))
|
||||||
|
return value;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建 UnityWebRequest
|
||||||
|
/// </summary>
|
||||||
|
protected override void CreateWebRequest()
|
||||||
|
{
|
||||||
|
_webRequest = CreateUnityWebRequestHead(URL);
|
||||||
|
_webRequest.downloadHandler = null; // HEAD 请求不需要 DownloadHandler
|
||||||
|
ApplyRequestOptions(_args.Timeout, _args.WatchdogTime, _args.Headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 请求成功时的回调
|
||||||
|
/// </summary>
|
||||||
|
protected override void OnRequestSucceed()
|
||||||
|
{
|
||||||
|
var headers = _webRequest.GetResponseHeaders();
|
||||||
|
if (headers != null)
|
||||||
|
{
|
||||||
|
_cachedResponseHeaders = new Dictionary<string, string>(headers.Count, StringComparer.OrdinalIgnoreCase);
|
||||||
|
foreach (var kvp in headers)
|
||||||
|
{
|
||||||
|
string name = kvp.Key.ToLowerInvariant();
|
||||||
|
string value = kvp.Value;
|
||||||
|
_cachedResponseHeaders[name] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 8ed243d707130394aa85e20dee876d8e
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
using System;
|
||||||
|
using UnityEngine.Networking;
|
||||||
|
|
||||||
|
namespace YooAsset
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// UnityWebRequest 文本下载器
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 将下载内容解析为 UTF-8 文本字符串。
|
||||||
|
/// </remarks>
|
||||||
|
internal sealed class UnityWebRequestTextDownloader : UnityWebRequestDownloaderBase, IDownloadTextRequest
|
||||||
|
{
|
||||||
|
private readonly DownloadDataRequestArgs _args;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 下载结果(文本字符串)
|
||||||
|
/// </summary>
|
||||||
|
public string Result { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构造文本下载器
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="args">数据下载参数</param>
|
||||||
|
/// <param name="webRequestCreator">UnityWebRequest 创建器(可选)</param>
|
||||||
|
public UnityWebRequestTextDownloader(DownloadDataRequestArgs args, UnityWebRequestCreator webRequestCreator)
|
||||||
|
: base(args.URL, webRequestCreator)
|
||||||
|
{
|
||||||
|
_args = args;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建 UnityWebRequest
|
||||||
|
/// </summary>
|
||||||
|
protected override void CreateWebRequest()
|
||||||
|
{
|
||||||
|
var handler = new DownloadHandlerBuffer();
|
||||||
|
_webRequest = CreateUnityWebRequestGet(URL);
|
||||||
|
_webRequest.downloadHandler = handler;
|
||||||
|
_webRequest.disposeDownloadHandlerOnDispose = true;
|
||||||
|
ApplyRequestOptions(_args.Timeout, _args.WatchdogTime, _args.Headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 请求成功时的回调
|
||||||
|
/// </summary>
|
||||||
|
protected override void OnRequestSucceed()
|
||||||
|
{
|
||||||
|
Result = _webRequest.downloadHandler.text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 9b0bd3024ca5bae4dbdc05c7af7479df
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace YooAsset
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 模拟下载器
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 用于编辑器模式下模拟下载进度,不进行实际网络请求。
|
||||||
|
/// 根据配置的下载速度模拟进度变化。
|
||||||
|
/// </remarks>
|
||||||
|
internal sealed class VirtualFileDownloader : IDownloadFileRequest
|
||||||
|
{
|
||||||
|
private readonly DownloadSimulateRequestArgs _args;
|
||||||
|
private double _lastUpdateTime;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 文件保存路径(模拟下载不需要)
|
||||||
|
/// </summary>
|
||||||
|
public string SavePath
|
||||||
|
{
|
||||||
|
get { return null; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#region 接口实现
|
||||||
|
/// <summary>
|
||||||
|
/// 请求地址
|
||||||
|
/// </summary>
|
||||||
|
public string URL { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否完成
|
||||||
|
/// </summary>
|
||||||
|
public bool IsDone
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
PollingRequest();
|
||||||
|
return Status == EDownloadRequestStatus.Succeed
|
||||||
|
|| Status == EDownloadRequestStatus.Failed
|
||||||
|
|| Status == EDownloadRequestStatus.Aborted;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 请求状态
|
||||||
|
/// </summary>
|
||||||
|
public EDownloadRequestStatus Status { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前下载进度(0f - 1f)
|
||||||
|
/// </summary>
|
||||||
|
public float DownloadProgress { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前请求已接收的字节数
|
||||||
|
/// </summary>
|
||||||
|
public long DownloadedBytes { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// HTTP 返回码(模拟固定返回 200)
|
||||||
|
/// </summary>
|
||||||
|
public long HttpCode { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 错误信息
|
||||||
|
/// </summary>
|
||||||
|
public string Error { get; private set; }
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构造模拟下载器
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="args">模拟下载参数</param>
|
||||||
|
public VirtualFileDownloader(DownloadSimulateRequestArgs args)
|
||||||
|
{
|
||||||
|
_args = args;
|
||||||
|
URL = args.URL;
|
||||||
|
Status = EDownloadRequestStatus.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发起请求
|
||||||
|
/// </summary>
|
||||||
|
public void SendRequest()
|
||||||
|
{
|
||||||
|
if (Status == EDownloadRequestStatus.None)
|
||||||
|
{
|
||||||
|
Status = EDownloadRequestStatus.Running;
|
||||||
|
_lastUpdateTime = GetUnityEngineRealtime();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 轮询请求
|
||||||
|
/// </summary>
|
||||||
|
public void PollingRequest()
|
||||||
|
{
|
||||||
|
if (Status != EDownloadRequestStatus.Running)
|
||||||
|
return;
|
||||||
|
|
||||||
|
double currentTime = GetUnityEngineRealtime();
|
||||||
|
double deltaTime = currentTime - _lastUpdateTime;
|
||||||
|
_lastUpdateTime = currentTime;
|
||||||
|
|
||||||
|
// 计算本帧下载的字节数
|
||||||
|
long downloadBytes = (long)(_args.DownloadSpeed * deltaTime);
|
||||||
|
DownloadedBytes += downloadBytes;
|
||||||
|
|
||||||
|
if (_args.FileSize > 0)
|
||||||
|
DownloadProgress = (float)DownloadedBytes / _args.FileSize;
|
||||||
|
|
||||||
|
// 检查是否完成
|
||||||
|
if (DownloadedBytes >= _args.FileSize)
|
||||||
|
{
|
||||||
|
HttpCode = 200;
|
||||||
|
DownloadProgress = 1f;
|
||||||
|
DownloadedBytes = _args.FileSize;
|
||||||
|
Status = EDownloadRequestStatus.Succeed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 中止请求
|
||||||
|
/// </summary>
|
||||||
|
public void AbortRequest()
|
||||||
|
{
|
||||||
|
if (Status == EDownloadRequestStatus.None || Status == EDownloadRequestStatus.Running)
|
||||||
|
{
|
||||||
|
Status = EDownloadRequestStatus.Aborted;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 释放资源
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private double GetUnityEngineRealtime()
|
||||||
|
{
|
||||||
|
#if UNITY_2020_3_OR_NEWER
|
||||||
|
return UnityEngine.Time.realtimeSinceStartupAsDouble;
|
||||||
|
#else
|
||||||
|
return UnityEngine.Time.realtimeSinceStartup;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 84daeb1559aadff40b5b5aa5c81d7d64
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
|
|
||||||
namespace YooAsset
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 下载器结束
|
|
||||||
/// </summary>
|
|
||||||
public struct DownloaderFinishData
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 所属包裹名称
|
|
||||||
/// </summary>
|
|
||||||
public string PackageName;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 是否成功
|
|
||||||
/// </summary>
|
|
||||||
public bool Succeed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 下载器相关的更新数据
|
|
||||||
/// </summary>
|
|
||||||
public struct DownloadUpdateData
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 所属包裹名称
|
|
||||||
/// </summary>
|
|
||||||
public string PackageName;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 下载进度 (0-1f)
|
|
||||||
/// </summary>
|
|
||||||
public float Progress;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 下载文件总数
|
|
||||||
/// </summary>
|
|
||||||
public int TotalDownloadCount;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 当前完成的下载文件数量
|
|
||||||
/// </summary>
|
|
||||||
public int CurrentDownloadCount;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 下载数据总大小(单位:字节)
|
|
||||||
/// </summary>
|
|
||||||
public long TotalDownloadBytes;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 当前完成的下载数据大小(单位:字节)
|
|
||||||
/// </summary>
|
|
||||||
public long CurrentDownloadBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 下载器相关的错误数据
|
|
||||||
/// </summary>
|
|
||||||
public struct DownloadErrorData
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 所属包裹名称
|
|
||||||
/// </summary>
|
|
||||||
public string PackageName;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 下载失败的文件名称
|
|
||||||
/// </summary>
|
|
||||||
public string FileName;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 错误信息
|
|
||||||
/// </summary>
|
|
||||||
public string ErrorInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 下载器相关的文件数据
|
|
||||||
/// </summary>
|
|
||||||
public struct DownloadFileData
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 所属包裹名称
|
|
||||||
/// </summary>
|
|
||||||
public string PackageName;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 下载的文件名称
|
|
||||||
/// </summary>
|
|
||||||
public string FileName;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 下载的文件大小
|
|
||||||
/// </summary>
|
|
||||||
public long FileSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 导入文件的信息
|
|
||||||
/// </summary>
|
|
||||||
public struct ImportFileInfo
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 本地文件路径
|
|
||||||
/// </summary>
|
|
||||||
public string FilePath;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 资源包名称
|
|
||||||
/// </summary>
|
|
||||||
public string BundleName;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 资源包GUID
|
|
||||||
/// </summary>
|
|
||||||
public string BundleGUID;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
443
Assets/YooAsset/Runtime/DownloadSystem/DownloadSystemDefine.cs
Normal file
443
Assets/YooAsset/Runtime/DownloadSystem/DownloadSystemDefine.cs
Normal file
@@ -0,0 +1,443 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace YooAsset
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 下载器结束
|
||||||
|
/// </summary>
|
||||||
|
public struct DownloaderFinishData
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 所属包裹名称
|
||||||
|
/// </summary>
|
||||||
|
public string PackageName;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否成功
|
||||||
|
/// </summary>
|
||||||
|
public bool Succeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 下载器相关的更新数据
|
||||||
|
/// </summary>
|
||||||
|
public struct DownloadUpdateData
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 所属包裹名称
|
||||||
|
/// </summary>
|
||||||
|
public string PackageName;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 下载进度 (0-1f)
|
||||||
|
/// </summary>
|
||||||
|
public float Progress;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 下载文件总数
|
||||||
|
/// </summary>
|
||||||
|
public int TotalDownloadCount;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前完成的下载文件数量
|
||||||
|
/// </summary>
|
||||||
|
public int CurrentDownloadCount;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 下载数据总大小(单位:字节)
|
||||||
|
/// </summary>
|
||||||
|
public long TotalDownloadBytes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前完成的下载数据大小(单位:字节)
|
||||||
|
/// </summary>
|
||||||
|
public long CurrentDownloadBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 下载器相关的错误数据
|
||||||
|
/// </summary>
|
||||||
|
public struct DownloadErrorData
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 所属包裹名称
|
||||||
|
/// </summary>
|
||||||
|
public string PackageName;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 下载失败的文件名称
|
||||||
|
/// </summary>
|
||||||
|
public string FileName;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 错误信息
|
||||||
|
/// </summary>
|
||||||
|
public string ErrorInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 下载器相关的文件数据
|
||||||
|
/// </summary>
|
||||||
|
public struct DownloadFileData
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 所属包裹名称
|
||||||
|
/// </summary>
|
||||||
|
public string PackageName;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 下载的文件名称
|
||||||
|
/// </summary>
|
||||||
|
public string FileName;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 下载的文件大小
|
||||||
|
/// </summary>
|
||||||
|
public long FileSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 导入文件的信息
|
||||||
|
/// </summary>
|
||||||
|
public struct ImportFileInfo
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 本地文件路径
|
||||||
|
/// </summary>
|
||||||
|
public string FilePath;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 资源包名称
|
||||||
|
/// </summary>
|
||||||
|
public string BundleName;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 资源包GUID
|
||||||
|
/// </summary>
|
||||||
|
public string BundleGUID;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 下载请求状态
|
||||||
|
/// </summary>
|
||||||
|
internal enum EDownloadRequestStatus
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 未开始
|
||||||
|
/// </summary>
|
||||||
|
None,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 进行中
|
||||||
|
/// </summary>
|
||||||
|
Running,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 已成功
|
||||||
|
/// </summary>
|
||||||
|
Succeed,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 已失败
|
||||||
|
/// </summary>
|
||||||
|
Failed,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 已中止
|
||||||
|
/// </summary>
|
||||||
|
Aborted,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 文件下载请求参数
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 用于将下载内容保存到本地文件的请求配置。
|
||||||
|
/// 支持断点续传和追加写入模式。
|
||||||
|
/// </remarks>
|
||||||
|
internal struct DownloadFileRequestArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 请求地址
|
||||||
|
/// </summary>
|
||||||
|
public readonly string URL;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 响应的超时时间(单位:秒)
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 当 Timeout 设置为 0 时,不应用超时。
|
||||||
|
/// 设置的超时值可能应用于Android上的每个URL重定向,这可能会导致响应时间增加。
|
||||||
|
/// </remarks>
|
||||||
|
public readonly int Timeout;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 看门狗超时时间(单位:秒)
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 用于监控下载任务的数据接收情况。
|
||||||
|
/// 规则说明:
|
||||||
|
/// 1. 当设置值为 0 时,表示禁用看门狗监控。
|
||||||
|
/// 2. 每次接收到下载数据时,看门狗计时器会重置。
|
||||||
|
/// 3. 若在设定的时间范围内未收到任何数据,任务将被自动终止。
|
||||||
|
/// </remarks>
|
||||||
|
public readonly int WatchdogTime;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 文件保存路径
|
||||||
|
/// </summary>
|
||||||
|
public readonly string SavePath;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否追加写入文件
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 配合 ResumeFromBytes 使用,用于断点续传场景。
|
||||||
|
/// </remarks>
|
||||||
|
public readonly bool AppendToFile;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 中止请求时是否删除目标文件
|
||||||
|
/// </summary>
|
||||||
|
public readonly bool RemoveFileOnAbort;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 断点续传的起始字节(小于等于 0 表示不启用)
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 推荐由后端自动设置 Range 请求头:"bytes={ResumeFromBytes}-"。
|
||||||
|
/// </remarks>
|
||||||
|
public readonly long ResumeFromBytes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 自定义请求头(可选)
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, string> Headers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构造文件下载请求参数
|
||||||
|
/// </summary>
|
||||||
|
public DownloadFileRequestArgs(
|
||||||
|
string url,
|
||||||
|
string savePath,
|
||||||
|
int timeout,
|
||||||
|
int watchdogTime,
|
||||||
|
bool appendToFile = false,
|
||||||
|
bool removeFileOnAbort = true,
|
||||||
|
long resumeFromBytes = 0)
|
||||||
|
{
|
||||||
|
URL = url;
|
||||||
|
SavePath = savePath;
|
||||||
|
Timeout = timeout;
|
||||||
|
WatchdogTime = watchdogTime;
|
||||||
|
AppendToFile = appendToFile;
|
||||||
|
RemoveFileOnAbort = removeFileOnAbort;
|
||||||
|
ResumeFromBytes = resumeFromBytes;
|
||||||
|
Headers = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 添加请求头数据
|
||||||
|
/// </summary>
|
||||||
|
public void AddRequestHeader(string name, string value)
|
||||||
|
{
|
||||||
|
if (Headers == null)
|
||||||
|
Headers = new Dictionary<string, string>(10);
|
||||||
|
Headers.Add(name, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 数据下载请求参数(通用)
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 用于下载到内存的请求配置。
|
||||||
|
/// 可用于字节数组(bytes)或文本(text)下载。
|
||||||
|
/// </remarks>
|
||||||
|
internal struct DownloadDataRequestArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 请求地址
|
||||||
|
/// </summary>
|
||||||
|
public readonly string URL;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 响应的超时时间(单位:秒)
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 当 Timeout 设置为 0 时,不应用超时。
|
||||||
|
/// 设置的超时值可能应用于Android上的每个URL重定向,这可能会导致响应时间增加。
|
||||||
|
/// </remarks>
|
||||||
|
public readonly int Timeout;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 看门狗超时时间(单位:秒)
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 用于监控下载任务的数据接收情况。
|
||||||
|
/// 规则说明:
|
||||||
|
/// 1. 当设置值为 0 时,表示禁用看门狗监控。
|
||||||
|
/// 2. 每次接收到下载数据时,看门狗计时器会重置。
|
||||||
|
/// 3. 若在设定的时间范围内未收到任何数据,任务将被自动终止。
|
||||||
|
/// </remarks>
|
||||||
|
public readonly int WatchdogTime;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 自定义请求头(可选)
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, string> Headers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构造数据下载请求参数
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="url">请求地址</param>
|
||||||
|
/// <param name="options">通用请求参数</param>
|
||||||
|
public DownloadDataRequestArgs(string url, int timeout, int watchdogTime)
|
||||||
|
{
|
||||||
|
URL = url;
|
||||||
|
Timeout = timeout;
|
||||||
|
WatchdogTime = watchdogTime;
|
||||||
|
Headers = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 添加请求头数据
|
||||||
|
/// </summary>
|
||||||
|
public void AddRequestHeader(string name, string value)
|
||||||
|
{
|
||||||
|
if (Headers == null)
|
||||||
|
Headers = new Dictionary<string, string>(10);
|
||||||
|
Headers.Add(name, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AssetBundle 下载请求参数
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 用于下载并加载 Unity AssetBundle 的请求配置。
|
||||||
|
/// 支持 Unity 内置缓存机制和 CRC 校验。
|
||||||
|
/// </remarks>
|
||||||
|
internal struct DownloadAssetBundleRequestArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 请求地址
|
||||||
|
/// </summary>
|
||||||
|
public readonly string URL;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 响应的超时时间(单位:秒)
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 当 Timeout 设置为 0 时,不应用超时。
|
||||||
|
/// 设置的超时值可能应用于Android上的每个URL重定向,这可能会导致响应时间增加。
|
||||||
|
/// </remarks>
|
||||||
|
public readonly int Timeout;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 看门狗超时时间(单位:秒)
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 用于监控下载任务的数据接收情况。
|
||||||
|
/// 规则说明:
|
||||||
|
/// 1. 当设置值为 0 时,表示禁用看门狗监控。
|
||||||
|
/// 2. 每次接收到下载数据时,看门狗计时器会重置。
|
||||||
|
/// 3. 若在设定的时间范围内未收到任何数据,任务将被自动终止。
|
||||||
|
/// </remarks>
|
||||||
|
public readonly int WatchdogTime;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 禁用 Unity 的网络缓存
|
||||||
|
/// </summary>
|
||||||
|
public readonly bool DisableUnityWebCache;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AssetBundle 文件哈希(用于 UnityWebRequest 的缓存)
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 仅当 DisableUnityWebCache 为 false 时需要。
|
||||||
|
/// </remarks>
|
||||||
|
public readonly string FileHash;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unity CRC 校验值
|
||||||
|
/// </summary>
|
||||||
|
public readonly uint UnityCRC;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 自定义请求头(可选)
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, string> Headers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构造 AssetBundle 下载请求参数
|
||||||
|
/// </summary>
|
||||||
|
public DownloadAssetBundleRequestArgs(
|
||||||
|
string url,
|
||||||
|
int timeout,
|
||||||
|
int watchdogTime,
|
||||||
|
bool disableUnityWebCache = true,
|
||||||
|
string fileHash = null,
|
||||||
|
uint unityCrc = 0)
|
||||||
|
{
|
||||||
|
URL = url;
|
||||||
|
Timeout = timeout;
|
||||||
|
WatchdogTime = watchdogTime;
|
||||||
|
DisableUnityWebCache = disableUnityWebCache;
|
||||||
|
FileHash = fileHash;
|
||||||
|
UnityCRC = unityCrc;
|
||||||
|
Headers = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 添加请求头数据
|
||||||
|
/// </summary>
|
||||||
|
public void AddRequestHeader(string name, string value)
|
||||||
|
{
|
||||||
|
if (Headers == null)
|
||||||
|
Headers = new Dictionary<string, string>(10);
|
||||||
|
Headers.Add(name, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 模拟下载请求参数
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 用于编辑器模式下模拟下载进度,不进行实际网络请求。
|
||||||
|
/// </remarks>
|
||||||
|
internal struct DownloadSimulateRequestArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 请求地址(仅用于标识)
|
||||||
|
/// </summary>
|
||||||
|
public readonly string URL;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 模拟的文件大小(字节)
|
||||||
|
/// </summary>
|
||||||
|
public readonly long FileSize;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 模拟的下载速度(字节/秒)
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 用于计算模拟的下载进度。
|
||||||
|
/// </remarks>
|
||||||
|
public readonly long DownloadSpeed;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构造模拟下载请求参数
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="url">请求地址(仅用于标识)</param>
|
||||||
|
/// <param name="fileSize">模拟的文件大小(字节)</param>
|
||||||
|
/// <param name="downloadSpeed">模拟的下载速度(字节/秒),默认 1MB/s</param>
|
||||||
|
public DownloadSimulateRequestArgs(string url, long fileSize, long downloadSpeed = 1024 * 1024)
|
||||||
|
{
|
||||||
|
URL = url;
|
||||||
|
FileSize = fileSize;
|
||||||
|
DownloadSpeed = downloadSpeed > 0 ? downloadSpeed : 1024 * 1024;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: eb0480e877454f7c944f66a01646b6d4
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -3,32 +3,8 @@ using UnityEngine;
|
|||||||
|
|
||||||
namespace YooAsset
|
namespace YooAsset
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// 自定义下载器的请求委托
|
|
||||||
/// </summary>
|
|
||||||
public delegate UnityWebRequest UnityWebRequestDelegate(string url);
|
|
||||||
|
|
||||||
internal class DownloadSystemHelper
|
internal class DownloadSystemHelper
|
||||||
{
|
{
|
||||||
#if UNITY_EDITOR
|
|
||||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
|
||||||
private static void OnRuntimeInitialize()
|
|
||||||
{
|
|
||||||
UnityWebRequestCreater = null;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
public static UnityWebRequestDelegate UnityWebRequestCreater = null;
|
|
||||||
public static UnityWebRequest NewUnityWebRequestGet(string requestURL)
|
|
||||||
{
|
|
||||||
UnityWebRequest webRequest;
|
|
||||||
if (UnityWebRequestCreater != null)
|
|
||||||
webRequest = UnityWebRequestCreater.Invoke(requestURL);
|
|
||||||
else
|
|
||||||
webRequest = new UnityWebRequest(requestURL, UnityWebRequest.kHttpVerbGET);
|
|
||||||
return webRequest;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取WWW加载本地资源的路径
|
/// 获取WWW加载本地资源的路径
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -81,7 +57,7 @@ namespace YooAsset
|
|||||||
#elif UNITY_STANDALONE_LINUX
|
#elif UNITY_STANDALONE_LINUX
|
||||||
url = StringUtility.Format("file:///root/{0}", path);
|
url = StringUtility.Format("file:///root/{0}", path);
|
||||||
#else
|
#else
|
||||||
throw new System.NotImplementedException();
|
throw new System.NotSupportedException($"[{nameof(DownloadSystemHelper.ConvertToWWWPath)}] not implemented platform: {UnityEngine.Application.platform}");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// For some special cases when users have special characters in their devices, url paths can not be identified correctly.
|
// For some special cases when users have special characters in their devices, url paths can not be identified correctly.
|
||||||
@@ -94,8 +70,12 @@ namespace YooAsset
|
|||||||
public static bool IsRequestLocalFile(string url)
|
public static bool IsRequestLocalFile(string url)
|
||||||
{
|
{
|
||||||
//TODO UNITY_STANDALONE_OSX平台目前无法确定
|
//TODO UNITY_STANDALONE_OSX平台目前无法确定
|
||||||
|
|
||||||
|
// 本地文件传输协议
|
||||||
if (url.StartsWith("file:"))
|
if (url.StartsWith("file:"))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
// JAR文件协议
|
||||||
if (url.StartsWith("jar:file:"))
|
if (url.StartsWith("jar:file:"))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 4630dac2050606043bb146325fdce6ad
|
guid: d724672f4d6f24b4db435d10eef6c40d
|
||||||
folderAsset: yes
|
folderAsset: yes
|
||||||
DefaultImporter:
|
DefaultImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace YooAsset
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 下载后台接口
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 不同网络库(UnityWebRequest / BestHTTP / 自研)实现该接口,用于创建具体下载请求。
|
||||||
|
/// 每个后台实例是独立的,不共享全局状态。
|
||||||
|
/// </remarks>
|
||||||
|
internal interface IDownloadBackend : IDisposable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 后台名称(用于日志与调试)
|
||||||
|
/// </summary>
|
||||||
|
string Name { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 驱动更新
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 部分第三方网络库需要在 Unity 主线程中周期性调用 Update 进行驱动。
|
||||||
|
/// 不需要驱动的后台可实现为空方法。
|
||||||
|
/// </remarks>
|
||||||
|
void Update();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建 HEAD 请求
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 仅获取响应头信息,不下载实际内容。
|
||||||
|
/// 用于检查资源是否存在、获取资源大小、检查缓存有效性等场景。
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="args">数据请求参数</param>
|
||||||
|
/// <returns>HEAD 请求实例</returns>
|
||||||
|
IDownloadHeadRequest CreateHeadRequest(DownloadDataRequestArgs args);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建文件下载请求
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="args">文件下载参数</param>
|
||||||
|
/// <returns>文件下载请求实例</returns>
|
||||||
|
IDownloadFileRequest CreateFileRequest(DownloadFileRequestArgs args);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建内存下载请求(字节数组)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="args">数据下载参数</param>
|
||||||
|
/// <returns>字节下载请求实例</returns>
|
||||||
|
IDownloadBytesRequest CreateBytesRequest(DownloadDataRequestArgs args);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建文本下载请求
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="args">数据下载参数</param>
|
||||||
|
/// <returns>文本下载请求实例</returns>
|
||||||
|
IDownloadTextRequest CreateTextRequest(DownloadDataRequestArgs args);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建 AssetBundle 下载请求
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="args">AssetBundle 下载参数</param>
|
||||||
|
/// <returns>AssetBundle 下载请求实例</returns>
|
||||||
|
IDownloadAssetBundleRequest CreateAssetBundleRequest(DownloadAssetBundleRequestArgs args);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建模拟下载请求
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 用于编辑器模式下模拟下载进度,不进行实际网络请求。
|
||||||
|
/// 可用于测试下载流程和 UI 展示。
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="args">模拟下载参数</param>
|
||||||
|
/// <returns>模拟下载请求实例</returns>
|
||||||
|
IDownloadFileRequest CreateSimulateRequest(DownloadSimulateRequestArgs args);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: e88c026ebbfb40258aad52ad1c2feb70
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,194 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace YooAsset
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 可轮询的下载请求接口
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 上层通常会在每帧轮询 Status IsDone,并在完成后读取结果。
|
||||||
|
/// </remarks>
|
||||||
|
internal interface IDownloadRequest : IDisposable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 请求地址
|
||||||
|
/// </summary>
|
||||||
|
string URL { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否完成(成功/失败/中止)
|
||||||
|
/// </summary>
|
||||||
|
bool IsDone { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 请求状态
|
||||||
|
/// </summary>
|
||||||
|
EDownloadRequestStatus Status { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前下载进度(0f - 1f)
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 部分情况下无法准确获取总长度,可返回 0。
|
||||||
|
/// </remarks>
|
||||||
|
float DownloadProgress { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前请求已接收的字节数
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 断点续传场景下,该值仅表示"本次请求新增下载的字节数",不包含已存在的本地文件长度。
|
||||||
|
/// </remarks>
|
||||||
|
long DownloadedBytes { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// HTTP 返回码
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 非 HTTP 协议可返回 0。使用 long 类型以兼容各种协议的返回码。
|
||||||
|
/// </remarks>
|
||||||
|
long HttpCode { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 错误信息
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 失败时不为空。
|
||||||
|
/// </remarks>
|
||||||
|
string Error { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发起请求
|
||||||
|
/// </summary>
|
||||||
|
void SendRequest();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 轮询请求
|
||||||
|
/// </summary>
|
||||||
|
void PollingRequest();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 中止请求
|
||||||
|
/// </summary>
|
||||||
|
void AbortRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// HEAD 请求接口(仅获取响应头)
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 用于检查资源是否存在、获取资源大小、检查缓存有效性等场景。
|
||||||
|
/// 不下载实际内容,仅获取响应头信息。
|
||||||
|
/// </remarks>
|
||||||
|
internal interface IDownloadHeadRequest : IDownloadRequest
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 获取 ETag 响应头
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 用于缓存验证,如果服务器未返回则为 null。
|
||||||
|
/// </remarks>
|
||||||
|
string ETag { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取 Last-Modified 响应头
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 资源最后修改时间,如果服务器未返回则为 null。
|
||||||
|
/// </remarks>
|
||||||
|
string LastModified { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取 Content-Type 响应头
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 资源的 MIME 类型,如果服务器未返回则为 null。
|
||||||
|
/// </remarks>
|
||||||
|
string ContentType { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 预期下载的总字节数(Content-Length)
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 从响应头 Content-Length 获取。
|
||||||
|
/// 如果服务器未返回或请求未完成,返回 -1。
|
||||||
|
/// 用于更准确的进度计算。
|
||||||
|
/// </remarks>
|
||||||
|
long ContentLength { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取响应头信息
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">响应头名称(不区分大小写)</param>
|
||||||
|
/// <returns>响应头的值,如果不存在或请求未完成则返回 null</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// 常用响应头:Content-Length、Content-Type、ETag、Last-Modified 等。
|
||||||
|
/// </remarks>
|
||||||
|
string GetResponseHeader(string name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 文件下载请求接口
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 将下载内容保存到指定的本地文件路径。
|
||||||
|
/// </remarks>
|
||||||
|
internal interface IDownloadFileRequest : IDownloadRequest
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 文件保存路径
|
||||||
|
/// </summary>
|
||||||
|
string SavePath { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 内存下载请求接口(字节数组)
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 将下载内容保存到内存中的字节数组。
|
||||||
|
/// </remarks>
|
||||||
|
internal interface IDownloadBytesRequest : IDownloadRequest
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 下载结果(字节数组)
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 仅在请求成功时可用,失败时为 null。
|
||||||
|
/// </remarks>
|
||||||
|
byte[] Result { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 文本下载请求接口
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 将下载内容解析为 UTF-8 文本字符串。
|
||||||
|
/// </remarks>
|
||||||
|
internal interface IDownloadTextRequest : IDownloadRequest
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 下载结果(文本字符串)
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 仅在请求成功时可用,失败时为 null。
|
||||||
|
/// </remarks>
|
||||||
|
string Result { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AssetBundle 下载请求接口
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 下载并加载 Unity AssetBundle 资源包。
|
||||||
|
/// </remarks>
|
||||||
|
internal interface IDownloadAssetBundleRequest : IDownloadRequest
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 下载结果(AssetBundle 对象)
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 仅在请求成功时可用,失败时为 null。
|
||||||
|
/// </remarks>
|
||||||
|
UnityEngine.AssetBundle Result { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 2a2aafeb309243f4959394a97d66b19f
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
using UnityEngine.Networking;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace YooAsset
|
|
||||||
{
|
|
||||||
internal class UnityAssetBundleRequestOperation : UnityWebRequestOperation
|
|
||||||
{
|
|
||||||
protected enum ESteps
|
|
||||||
{
|
|
||||||
None,
|
|
||||||
CreateRequest,
|
|
||||||
Download,
|
|
||||||
Done,
|
|
||||||
}
|
|
||||||
|
|
||||||
private UnityWebRequestAsyncOperation _requestOperation;
|
|
||||||
private DownloadHandlerAssetBundle _downloadhandler;
|
|
||||||
private readonly PackageBundle _packageBundle;
|
|
||||||
private readonly bool _disableUnityWebCache;
|
|
||||||
private ESteps _steps = ESteps.None;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 请求结果
|
|
||||||
/// </summary>
|
|
||||||
public AssetBundle Result { private set; get; }
|
|
||||||
|
|
||||||
internal UnityAssetBundleRequestOperation(PackageBundle packageBundle, bool disableUnityWebCache, string url) : base(url)
|
|
||||||
{
|
|
||||||
_packageBundle = packageBundle;
|
|
||||||
_disableUnityWebCache = disableUnityWebCache;
|
|
||||||
}
|
|
||||||
internal override void InternalStart()
|
|
||||||
{
|
|
||||||
_steps = ESteps.CreateRequest;
|
|
||||||
}
|
|
||||||
internal override void InternalUpdate()
|
|
||||||
{
|
|
||||||
if (_steps == ESteps.None || _steps == ESteps.Done)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (_steps == ESteps.CreateRequest)
|
|
||||||
{
|
|
||||||
CreateWebRequest();
|
|
||||||
_steps = ESteps.Download;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_steps == ESteps.Download)
|
|
||||||
{
|
|
||||||
DownloadProgress = _webRequest.downloadProgress;
|
|
||||||
DownloadedBytes = (long)_webRequest.downloadedBytes;
|
|
||||||
Progress = _requestOperation.progress;
|
|
||||||
if (_requestOperation.isDone == false)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (CheckRequestResult())
|
|
||||||
{
|
|
||||||
AssetBundle assetBundle = _downloadhandler.assetBundle;
|
|
||||||
if (assetBundle == null)
|
|
||||||
{
|
|
||||||
_steps = ESteps.Done;
|
|
||||||
Status = EOperationStatus.Failed;
|
|
||||||
Error = $"URL : {_requestURL} Download handler asset bundle object is null !";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_steps = ESteps.Done;
|
|
||||||
Result = assetBundle;
|
|
||||||
Status = EOperationStatus.Succeed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_steps = ESteps.Done;
|
|
||||||
Status = EOperationStatus.Failed;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 注意:最终释放请求器
|
|
||||||
DisposeRequest();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CreateWebRequest()
|
|
||||||
{
|
|
||||||
_downloadhandler = CreateWebDownloadHandler();
|
|
||||||
_webRequest = DownloadSystemHelper.NewUnityWebRequestGet(_requestURL);
|
|
||||||
_webRequest.downloadHandler = _downloadhandler;
|
|
||||||
_webRequest.disposeDownloadHandlerOnDispose = true;
|
|
||||||
_requestOperation = _webRequest.SendWebRequest();
|
|
||||||
}
|
|
||||||
private DownloadHandlerAssetBundle CreateWebDownloadHandler()
|
|
||||||
{
|
|
||||||
if (_disableUnityWebCache)
|
|
||||||
{
|
|
||||||
var downloadhandler = new DownloadHandlerAssetBundle(_requestURL, _packageBundle.UnityCRC);
|
|
||||||
#if UNITY_2020_3_OR_NEWER
|
|
||||||
downloadhandler.autoLoadAssetBundle = false;
|
|
||||||
#endif
|
|
||||||
return downloadhandler;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 注意:优先从浏览器缓存里获取文件
|
|
||||||
// The file hash defining the version of the asset bundle.
|
|
||||||
Hash128 fileHash = Hash128.Parse(_packageBundle.FileHash);
|
|
||||||
var downloadhandler = new DownloadHandlerAssetBundle(_requestURL, fileHash, _packageBundle.UnityCRC);
|
|
||||||
#if UNITY_2020_3_OR_NEWER
|
|
||||||
downloadhandler.autoLoadAssetBundle = false;
|
|
||||||
#endif
|
|
||||||
return downloadhandler;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
using UnityEngine.Networking;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace YooAsset
|
|
||||||
{
|
|
||||||
internal class UnityVirtualBundleRequestOperation : UnityWebRequestOperation
|
|
||||||
{
|
|
||||||
protected enum ESteps
|
|
||||||
{
|
|
||||||
None,
|
|
||||||
Download,
|
|
||||||
Done,
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly PackageBundle _bundle;
|
|
||||||
private readonly int _downloadSpeed;
|
|
||||||
private ESteps _steps = ESteps.None;
|
|
||||||
|
|
||||||
internal UnityVirtualBundleRequestOperation(PackageBundle packageBundle, int downloadSpeed, string url) : base(url)
|
|
||||||
{
|
|
||||||
_bundle = packageBundle;
|
|
||||||
_downloadSpeed = downloadSpeed;
|
|
||||||
}
|
|
||||||
internal override void InternalStart()
|
|
||||||
{
|
|
||||||
_steps = ESteps.Download;
|
|
||||||
}
|
|
||||||
internal override void InternalUpdate()
|
|
||||||
{
|
|
||||||
if (_steps == ESteps.None || _steps == ESteps.Done)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (_steps == ESteps.Download)
|
|
||||||
{
|
|
||||||
// 模拟下载进度
|
|
||||||
float progress = 0;
|
|
||||||
if (DownloadedBytes > 0)
|
|
||||||
progress = DownloadedBytes / _bundle.FileSize;
|
|
||||||
long downloadBytes = (long)((double)_downloadSpeed * Time.deltaTime);
|
|
||||||
|
|
||||||
Progress = progress;
|
|
||||||
DownloadProgress = progress;
|
|
||||||
DownloadedBytes += downloadBytes;
|
|
||||||
if (DownloadedBytes < _bundle.FileSize)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Progress = 1f;
|
|
||||||
DownloadProgress = 1f;
|
|
||||||
DownloadedBytes = _bundle.FileSize;
|
|
||||||
|
|
||||||
_steps = ESteps.Done;
|
|
||||||
Status = EOperationStatus.Succeed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
internal override void InternalWaitForAsyncComplete()
|
|
||||||
{
|
|
||||||
if (_steps != ESteps.Done)
|
|
||||||
{
|
|
||||||
_steps = ESteps.Done;
|
|
||||||
Status = EOperationStatus.Failed;
|
|
||||||
Error = $"Try load bundle {_bundle.BundleName} from remote !";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using UnityEngine.Networking;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace YooAsset
|
|
||||||
{
|
|
||||||
internal class UnityWebCacheRequestOperation : UnityWebRequestOperation
|
|
||||||
{
|
|
||||||
protected enum ESteps
|
|
||||||
{
|
|
||||||
None,
|
|
||||||
CreateRequest,
|
|
||||||
Download,
|
|
||||||
Done,
|
|
||||||
}
|
|
||||||
|
|
||||||
private UnityWebRequestAsyncOperation _requestOperation;
|
|
||||||
private readonly Dictionary<string, string> _headers = new Dictionary<string, string>();
|
|
||||||
private ESteps _steps = ESteps.None;
|
|
||||||
|
|
||||||
|
|
||||||
internal UnityWebCacheRequestOperation(string url) : base(url)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
internal override void InternalStart()
|
|
||||||
{
|
|
||||||
_steps = ESteps.CreateRequest;
|
|
||||||
}
|
|
||||||
internal override void InternalUpdate()
|
|
||||||
{
|
|
||||||
if (_steps == ESteps.None || _steps == ESteps.Done)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (_steps == ESteps.CreateRequest)
|
|
||||||
{
|
|
||||||
CreateWebRequest();
|
|
||||||
_steps = ESteps.Download;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_steps == ESteps.Download)
|
|
||||||
{
|
|
||||||
DownloadProgress = _webRequest.downloadProgress;
|
|
||||||
DownloadedBytes = (long)_webRequest.downloadedBytes;
|
|
||||||
Progress = _requestOperation.progress;
|
|
||||||
if (_requestOperation.isDone == false)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (CheckRequestResult())
|
|
||||||
{
|
|
||||||
_steps = ESteps.Done;
|
|
||||||
Status = EOperationStatus.Succeed;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_steps = ESteps.Done;
|
|
||||||
Status = EOperationStatus.Failed;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 注意:最终释放请求器
|
|
||||||
DisposeRequest();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 设置请求头信息
|
|
||||||
/// </summary>
|
|
||||||
public void SetRequestHeader(string name, string value)
|
|
||||||
{
|
|
||||||
_headers.Add(name, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CreateWebRequest()
|
|
||||||
{
|
|
||||||
_webRequest = DownloadSystemHelper.NewUnityWebRequestGet(_requestURL);
|
|
||||||
_webRequest.disposeDownloadHandlerOnDispose = true;
|
|
||||||
|
|
||||||
// 设置消息头
|
|
||||||
foreach (var keyValuePair in _headers)
|
|
||||||
{
|
|
||||||
string name = keyValuePair.Key;
|
|
||||||
string value = keyValuePair.Value;
|
|
||||||
_webRequest.SetRequestHeader(name, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
_requestOperation = _webRequest.SendWebRequest();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
using UnityEngine.Networking;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace YooAsset
|
|
||||||
{
|
|
||||||
internal class UnityWebDataRequestOperation : UnityWebRequestOperation
|
|
||||||
{
|
|
||||||
protected enum ESteps
|
|
||||||
{
|
|
||||||
None,
|
|
||||||
CreateRequest,
|
|
||||||
Download,
|
|
||||||
Done,
|
|
||||||
}
|
|
||||||
|
|
||||||
private UnityWebRequestAsyncOperation _requestOperation;
|
|
||||||
private ESteps _steps = ESteps.None;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 响应的超时时间(单位:秒),在经过Timeout的秒数后尝试中止。
|
|
||||||
/// 注意:当Timeout设置为0时,不会应用超时。
|
|
||||||
/// 注意:设置的超时值可能应用于Android上的每个URL重定向,这可能会导致响应时间增加。
|
|
||||||
/// </summary>
|
|
||||||
private readonly int _timeout;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 请求结果
|
|
||||||
/// </summary>
|
|
||||||
public byte[] Result { private set; get; }
|
|
||||||
|
|
||||||
|
|
||||||
internal UnityWebDataRequestOperation(string url, int timeout) : base(url)
|
|
||||||
{
|
|
||||||
_timeout = timeout;
|
|
||||||
}
|
|
||||||
internal override void InternalStart()
|
|
||||||
{
|
|
||||||
_steps = ESteps.CreateRequest;
|
|
||||||
}
|
|
||||||
internal override void InternalUpdate()
|
|
||||||
{
|
|
||||||
if (_steps == ESteps.None || _steps == ESteps.Done)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (_steps == ESteps.CreateRequest)
|
|
||||||
{
|
|
||||||
CreateWebRequest();
|
|
||||||
_steps = ESteps.Download;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_steps == ESteps.Download)
|
|
||||||
{
|
|
||||||
DownloadProgress = _webRequest.downloadProgress;
|
|
||||||
DownloadedBytes = (long)_webRequest.downloadedBytes;
|
|
||||||
Progress = _requestOperation.progress;
|
|
||||||
if (_requestOperation.isDone == false)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (CheckRequestResult())
|
|
||||||
{
|
|
||||||
var fileData = _webRequest.downloadHandler.data;
|
|
||||||
if (fileData == null || fileData.Length == 0)
|
|
||||||
{
|
|
||||||
_steps = ESteps.Done;
|
|
||||||
Status = EOperationStatus.Failed;
|
|
||||||
Error = $"URL : {_requestURL} Download handler data is null or empty !";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_steps = ESteps.Done;
|
|
||||||
Result = fileData;
|
|
||||||
Status = EOperationStatus.Succeed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_steps = ESteps.Done;
|
|
||||||
Status = EOperationStatus.Failed;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 注意:最终释放请求器
|
|
||||||
DisposeRequest();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CreateWebRequest()
|
|
||||||
{
|
|
||||||
DownloadHandlerBuffer handler = new DownloadHandlerBuffer();
|
|
||||||
_webRequest = DownloadSystemHelper.NewUnityWebRequestGet(_requestURL);
|
|
||||||
_webRequest.timeout = _timeout;
|
|
||||||
_webRequest.downloadHandler = handler;
|
|
||||||
_webRequest.disposeDownloadHandlerOnDispose = true;
|
|
||||||
_requestOperation = _webRequest.SendWebRequest();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: b7557eb146572de49a1ec9b3f3c0b706
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
using UnityEngine.Networking;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace YooAsset
|
|
||||||
{
|
|
||||||
internal class UnityWebFileRequestOperation : UnityWebRequestOperation
|
|
||||||
{
|
|
||||||
protected enum ESteps
|
|
||||||
{
|
|
||||||
None,
|
|
||||||
CreateRequest,
|
|
||||||
Download,
|
|
||||||
Done,
|
|
||||||
}
|
|
||||||
|
|
||||||
private UnityWebRequestAsyncOperation _requestOperation;
|
|
||||||
private readonly string _fileSavePath;
|
|
||||||
private ESteps _steps = ESteps.None;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 响应的超时时间(单位:秒),在经过Timeout的秒数后尝试中止。
|
|
||||||
/// 注意:当Timeout设置为0时,不会应用超时。
|
|
||||||
/// 注意:设置的超时值可能应用于Android上的每个URL重定向,这可能会导致响应时间增加。
|
|
||||||
/// </summary>
|
|
||||||
private readonly int _timeout;
|
|
||||||
|
|
||||||
|
|
||||||
internal UnityWebFileRequestOperation(string url, string fileSavePath, int timeout) : base(url)
|
|
||||||
{
|
|
||||||
_fileSavePath = fileSavePath;
|
|
||||||
_timeout = timeout;
|
|
||||||
}
|
|
||||||
internal override void InternalStart()
|
|
||||||
{
|
|
||||||
_steps = ESteps.CreateRequest;
|
|
||||||
}
|
|
||||||
internal override void InternalUpdate()
|
|
||||||
{
|
|
||||||
if (_steps == ESteps.None || _steps == ESteps.Done)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (_steps == ESteps.CreateRequest)
|
|
||||||
{
|
|
||||||
CreateWebRequest();
|
|
||||||
_steps = ESteps.Download;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_steps == ESteps.Download)
|
|
||||||
{
|
|
||||||
DownloadProgress = _webRequest.downloadProgress;
|
|
||||||
DownloadedBytes = (long)_webRequest.downloadedBytes;
|
|
||||||
Progress = _requestOperation.progress;
|
|
||||||
if (_requestOperation.isDone == false)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (CheckRequestResult())
|
|
||||||
{
|
|
||||||
_steps = ESteps.Done;
|
|
||||||
Status = EOperationStatus.Succeed;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_steps = ESteps.Done;
|
|
||||||
Status = EOperationStatus.Failed;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 注意:最终释放请求器
|
|
||||||
DisposeRequest();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CreateWebRequest()
|
|
||||||
{
|
|
||||||
DownloadHandlerFile handler = new DownloadHandlerFile(_fileSavePath);
|
|
||||||
handler.removeFileOnAbort = true;
|
|
||||||
_webRequest = DownloadSystemHelper.NewUnityWebRequestGet(_requestURL);
|
|
||||||
_webRequest.timeout = _timeout;
|
|
||||||
_webRequest.downloadHandler = handler;
|
|
||||||
_webRequest.disposeDownloadHandlerOnDispose = true;
|
|
||||||
_requestOperation = _webRequest.SendWebRequest();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 2ab5f2486d06e2642ba7aa9c9623430e
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
using System;
|
|
||||||
using UnityEngine.Networking;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace YooAsset
|
|
||||||
{
|
|
||||||
internal abstract class UnityWebRequestOperation : AsyncOperationBase
|
|
||||||
{
|
|
||||||
protected UnityWebRequest _webRequest;
|
|
||||||
protected readonly string _requestURL;
|
|
||||||
private bool _isAbort = false;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// HTTP返回码
|
|
||||||
/// </summary>
|
|
||||||
public long HttpCode { private set; get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 当前下载的字节数
|
|
||||||
/// </summary>
|
|
||||||
public long DownloadedBytes { protected set; get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 当前下载进度(0f - 1f)
|
|
||||||
/// </summary>
|
|
||||||
public float DownloadProgress { protected set; get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 请求的URL地址
|
|
||||||
/// </summary>
|
|
||||||
public string URL
|
|
||||||
{
|
|
||||||
get { return _requestURL; }
|
|
||||||
}
|
|
||||||
|
|
||||||
internal UnityWebRequestOperation(string url)
|
|
||||||
{
|
|
||||||
_requestURL = url;
|
|
||||||
}
|
|
||||||
internal override void InternalAbort()
|
|
||||||
{
|
|
||||||
//TODO
|
|
||||||
// 1. 编辑器下停止运行游戏的时候主动终止下载任务
|
|
||||||
// 2. 真机上销毁包裹的时候主动终止下载任务
|
|
||||||
if (_isAbort == false)
|
|
||||||
{
|
|
||||||
if (_webRequest != null)
|
|
||||||
{
|
|
||||||
_webRequest.Abort();
|
|
||||||
_isAbort = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 释放下载器
|
|
||||||
/// </summary>
|
|
||||||
protected void DisposeRequest()
|
|
||||||
{
|
|
||||||
if (_webRequest != null)
|
|
||||||
{
|
|
||||||
//注意:引擎底层会自动调用Abort方法
|
|
||||||
_webRequest.Dispose();
|
|
||||||
_webRequest = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 检测请求结果
|
|
||||||
/// </summary>
|
|
||||||
protected bool CheckRequestResult()
|
|
||||||
{
|
|
||||||
HttpCode = _webRequest.responseCode;
|
|
||||||
|
|
||||||
#if UNITY_2020_3_OR_NEWER
|
|
||||||
if (_webRequest.result != UnityWebRequest.Result.Success)
|
|
||||||
{
|
|
||||||
Error = $"URL : {_requestURL} Error : {_webRequest.error}";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
if (_webRequest.isNetworkError || _webRequest.isHttpError)
|
|
||||||
{
|
|
||||||
Error = $"URL : {_requestURL} Error : {_webRequest.error}";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 4e5c3c1a1655a8b41b585c6811201583
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
using UnityEngine.Networking;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace YooAsset
|
|
||||||
{
|
|
||||||
internal class UnityWebTextRequestOperation : UnityWebRequestOperation
|
|
||||||
{
|
|
||||||
protected enum ESteps
|
|
||||||
{
|
|
||||||
None,
|
|
||||||
CreateRequest,
|
|
||||||
Download,
|
|
||||||
Done,
|
|
||||||
}
|
|
||||||
|
|
||||||
private UnityWebRequestAsyncOperation _requestOperation;
|
|
||||||
private ESteps _steps = ESteps.None;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 响应的超时时间(单位:秒),在经过Timeout的秒数后尝试中止。
|
|
||||||
/// 注意:当Timeout设置为0时,不会应用超时。
|
|
||||||
/// 注意:设置的超时值可能应用于Android上的每个URL重定向,这可能会导致响应时间增加。
|
|
||||||
/// </summary>
|
|
||||||
private readonly int _timeout;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 请求结果
|
|
||||||
/// </summary>
|
|
||||||
public string Result { private set; get; }
|
|
||||||
|
|
||||||
|
|
||||||
internal UnityWebTextRequestOperation(string url, int timeout) : base(url)
|
|
||||||
{
|
|
||||||
_timeout = timeout;
|
|
||||||
}
|
|
||||||
internal override void InternalStart()
|
|
||||||
{
|
|
||||||
_steps = ESteps.CreateRequest;
|
|
||||||
}
|
|
||||||
internal override void InternalUpdate()
|
|
||||||
{
|
|
||||||
if (_steps == ESteps.None || _steps == ESteps.Done)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (_steps == ESteps.CreateRequest)
|
|
||||||
{
|
|
||||||
CreateWebRequest();
|
|
||||||
_steps = ESteps.Download;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_steps == ESteps.Download)
|
|
||||||
{
|
|
||||||
DownloadProgress = _webRequest.downloadProgress;
|
|
||||||
DownloadedBytes = (long)_webRequest.downloadedBytes;
|
|
||||||
Progress = _requestOperation.progress;
|
|
||||||
if (_requestOperation.isDone == false)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (CheckRequestResult())
|
|
||||||
{
|
|
||||||
var fileText = _webRequest.downloadHandler.text;
|
|
||||||
if (string.IsNullOrEmpty(fileText))
|
|
||||||
{
|
|
||||||
_steps = ESteps.Done;
|
|
||||||
Status = EOperationStatus.Failed;
|
|
||||||
Error = $"URL : {_requestURL} Download handler text is null or empty !";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_steps = ESteps.Done;
|
|
||||||
Result = fileText;
|
|
||||||
Status = EOperationStatus.Succeed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_steps = ESteps.Done;
|
|
||||||
Status = EOperationStatus.Failed;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 注意:最终释放请求器
|
|
||||||
DisposeRequest();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CreateWebRequest()
|
|
||||||
{
|
|
||||||
DownloadHandlerBuffer handler = new DownloadHandlerBuffer();
|
|
||||||
_webRequest = DownloadSystemHelper.NewUnityWebRequestGet(_requestURL);
|
|
||||||
_webRequest.timeout = _timeout;
|
|
||||||
_webRequest.downloadHandler = handler;
|
|
||||||
_webRequest.disposeDownloadHandlerOnDispose = true;
|
|
||||||
_requestOperation = _webRequest.SendWebRequest();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: a488de5dcd6f4c448a47c4b574d5c9bc
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
567
Assets/YooAsset/Runtime/DownloadSystem/README.md
Normal file
567
Assets/YooAsset/Runtime/DownloadSystem/README.md
Normal file
@@ -0,0 +1,567 @@
|
|||||||
|
# DownloadSystem 下载模块
|
||||||
|
|
||||||
|
## 模块概述
|
||||||
|
|
||||||
|
DownloadSystem 是 YooAsset 资源管理系统的**底层网络下载层**,负责处理所有 HTTP 网络请求。该模块提供了统一的下载接口抽象,支持文件下载、断点续传、并发请求(由上层调度)、看门狗监控等功能。
|
||||||
|
|
||||||
|
### 核心职责
|
||||||
|
|
||||||
|
- HTTP/HTTPS 文件下载
|
||||||
|
- 断点续传支持
|
||||||
|
- 看门狗超时保护
|
||||||
|
- 多种下载类型(文件/字节/文本/AssetBundle)
|
||||||
|
- 可插拔的网络库后端
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 边界与上层协作
|
||||||
|
|
||||||
|
DownloadSystem 的职责是提供“可替换后端 + 统一请求接口 + 轮询式生命周期”的基础能力:
|
||||||
|
|
||||||
|
- **本模块不负责并发队列/限流调度**:并发通常由上层同时创建多个 request 并自行控制并发数。
|
||||||
|
- **本模块不负责重试/回退策略**:失败后的重试、切换 CDN、降级等策略通常由上层系统实现。
|
||||||
|
- **本模块不负责持久化下载任务**:断点续传依赖本地已有文件与 `Range` 请求头,并由上层管理断点信息。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 设计目标
|
||||||
|
|
||||||
|
| 目标 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| **可扩展性** | 支持可插拔的网络库后端(UnityWebRequest/BestHTTP/自研) |
|
||||||
|
| **鲁棒性** | 看门狗超时保护、自动清理失败文件、完整错误信息 |
|
||||||
|
| **高性能** | 轮询模式无阻塞、支持并发请求(并发数由上层调度) |
|
||||||
|
| **易用性** | 流畅的参数构建 API、清晰的状态转换 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 架构概念
|
||||||
|
|
||||||
|
### 分层架构
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ 上层调用者 │
|
||||||
|
│ (FileSystem / ResourceManager) │
|
||||||
|
└─────────────────────────┬───────────────────────────────┘
|
||||||
|
│
|
||||||
|
┌─────────────────────────▼───────────────────────────────┐
|
||||||
|
│ IDownloadBackend │
|
||||||
|
│ (后端接口) │
|
||||||
|
│ 定义网络库合约,工厂模式创建请求 │
|
||||||
|
└─────────────────────────┬───────────────────────────────┘
|
||||||
|
│
|
||||||
|
┌─────────────────────────▼───────────────────────────────┐
|
||||||
|
│ IDownloadRequest │
|
||||||
|
│ (请求接口) │
|
||||||
|
│ 轮询式生命周期管理,状态机驱动 │
|
||||||
|
└─────────────────────────┬───────────────────────────────┘
|
||||||
|
│
|
||||||
|
┌─────────────────────────▼───────────────────────────────┐
|
||||||
|
│ UnityWebRequest / 其他网络库 │
|
||||||
|
│ (底层实现) │
|
||||||
|
└─────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 核心组件
|
||||||
|
|
||||||
|
- **后端层 (IDownloadBackend)**: 定义网络库实现合约,通过工厂方法创建各类请求
|
||||||
|
- **请求层 (IDownloadRequest)**: 统一的请求生命周期管理,支持轮询驱动
|
||||||
|
- **参数层 (Args 结构体)**: 配置下载行为(超时、断点续传、看门狗等)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 文件结构
|
||||||
|
|
||||||
|
```
|
||||||
|
DownloadSystem/
|
||||||
|
├── Interface/ # 接口定义
|
||||||
|
│ ├── IDownloadBackend.cs # 后端接口(工厂模式)
|
||||||
|
│ └── IDownloadRequest.cs # 请求接口层次结构
|
||||||
|
│
|
||||||
|
├── DefaultDownloadBackend/ # 默认后端实现
|
||||||
|
│ ├── UnityWebRequestBackend.cs # UnityWebRequest 后端
|
||||||
|
│ └── UnityWebRequestCreator.cs # UnityWebRequest 创建委托
|
||||||
|
│
|
||||||
|
├── DefaultDownloadRequest/ # 默认请求实现
|
||||||
|
│ ├── UnityWebRequestDownloaderBase.cs # 基础下载器(抽象类)
|
||||||
|
│ ├── UnityWebRequestFileDownloader.cs # 文件下载器
|
||||||
|
│ ├── UnityWebRequestHeadDownloader.cs # HEAD 请求器
|
||||||
|
│ ├── UnityWebRequestBytesDownloader.cs # 字节下载器
|
||||||
|
│ ├── UnityWebRequestTextDownloader.cs # 文本下载器
|
||||||
|
│ ├── UnityWebRequestAssetBundleDownloader.cs # AssetBundle 下载器
|
||||||
|
│ └── VirtualFileDownloader.cs # 模拟下载器(编辑器用)
|
||||||
|
│
|
||||||
|
├── DownloadSystemDefine.cs # 枚举、结构体定义
|
||||||
|
├── DownloadSystemHelper.cs # 工具函数
|
||||||
|
└── WebRequestCounter.cs # 请求失败计数器
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 接口说明
|
||||||
|
|
||||||
|
### IDownloadBackend(后端接口)
|
||||||
|
|
||||||
|
定义网络库实现的合约,通过工厂方法创建各类下载请求。
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public interface IDownloadBackend
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 后端标识名称(用于日志/调试)
|
||||||
|
/// </summary>
|
||||||
|
string Name { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 定期驱动更新(部分第三方库需要)
|
||||||
|
/// </summary>
|
||||||
|
void Update();
|
||||||
|
|
||||||
|
// 工厂方法 - 创建各类请求
|
||||||
|
IDownloadHeadRequest CreateHeadRequest(DownloadDataRequestArgs args);
|
||||||
|
IDownloadFileRequest CreateFileRequest(DownloadFileRequestArgs args);
|
||||||
|
IDownloadBytesRequest CreateBytesRequest(DownloadDataRequestArgs args);
|
||||||
|
IDownloadTextRequest CreateTextRequest(DownloadDataRequestArgs args);
|
||||||
|
IDownloadAssetBundleRequest CreateAssetBundleRequest(DownloadAssetBundleRequestArgs args);
|
||||||
|
IDownloadFileRequest CreateSimulateRequest(DownloadSimulateRequestArgs args);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### IDownloadRequest(基础请求接口)
|
||||||
|
|
||||||
|
所有下载请求的通用接口,定义生命周期和状态管理。
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public interface IDownloadRequest : IDisposable
|
||||||
|
{
|
||||||
|
// 元信息
|
||||||
|
string URL { get; }
|
||||||
|
|
||||||
|
// 生命周期
|
||||||
|
bool IsDone { get; } // 每次访问自动轮询
|
||||||
|
EDownloadRequestStatus Status { get; }
|
||||||
|
|
||||||
|
// 进度跟踪
|
||||||
|
float DownloadProgress { get; } // 0f - 1f
|
||||||
|
long DownloadedBytes { get; } // 本次请求新增字节数
|
||||||
|
|
||||||
|
// 诊断信息
|
||||||
|
long HttpCode { get; }
|
||||||
|
string Error { get; }
|
||||||
|
|
||||||
|
// 生命周期方法
|
||||||
|
void SendRequest(); // 发起请求
|
||||||
|
void PollingRequest(); // 轮询状态
|
||||||
|
void AbortRequest(); // 中止请求
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 专化请求接口
|
||||||
|
|
||||||
|
| 接口 | 用途 | 特有属性 |
|
||||||
|
|------|------|----------|
|
||||||
|
| `IDownloadHeadRequest` | HEAD 请求,获取响应头 | `ETag`, `LastModified`, `ContentLength`, `ContentType` |
|
||||||
|
| `IDownloadFileRequest` | 文件下载到本地 | `SavePath` |
|
||||||
|
| `IDownloadBytesRequest` | 下载到内存(字节数组) | `byte[] Result` |
|
||||||
|
| `IDownloadTextRequest` | 下载文本内容 | `string Result` |
|
||||||
|
| `IDownloadAssetBundleRequest` | 下载并加载 AssetBundle | `AssetBundle Result` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 结构体定义
|
||||||
|
|
||||||
|
### 请求状态枚举
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public enum EDownloadRequestStatus
|
||||||
|
{
|
||||||
|
None, // 未开始
|
||||||
|
Running, // 进行中
|
||||||
|
Succeed, // 已成功
|
||||||
|
Failed, // 已失败
|
||||||
|
Aborted // 已中止(用户中止或看门狗超时)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 请求参数结构体
|
||||||
|
|
||||||
|
#### DownloadFileRequestArgs(文件下载参数)
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public struct DownloadFileRequestArgs
|
||||||
|
{
|
||||||
|
public string URL; // 请求地址
|
||||||
|
public int Timeout; // 响应超时(秒),0=无限制
|
||||||
|
public int WatchdogTime; // 看门狗超时(秒)
|
||||||
|
|
||||||
|
public string SavePath; // 文件保存路径
|
||||||
|
public bool AppendToFile; // 追加写入(断点续传)
|
||||||
|
public bool RemoveFileOnAbort; // 中止时删除文件
|
||||||
|
public long ResumeFromBytes; // 断点续传起始位置
|
||||||
|
|
||||||
|
public Dictionary<string, string> Headers; // 自定义请求头
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### DownloadDataRequestArgs(数据下载参数)
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public struct DownloadDataRequestArgs
|
||||||
|
{
|
||||||
|
public string URL; // 请求地址
|
||||||
|
public int Timeout; // 响应超时(秒)
|
||||||
|
public int WatchdogTime; // 看门狗超时(秒)
|
||||||
|
public Dictionary<string, string> Headers; // 自定义请求头
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### DownloadAssetBundleRequestArgs(AssetBundle 下载参数)
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public struct DownloadAssetBundleRequestArgs
|
||||||
|
{
|
||||||
|
public string URL; // 请求地址
|
||||||
|
public int Timeout; // 响应超时
|
||||||
|
public int WatchdogTime; // 看门狗超时
|
||||||
|
|
||||||
|
public bool DisableUnityWebCache; // 禁用 Unity 缓存(推荐 true)
|
||||||
|
public string FileHash; // 文件哈希(缓存启用时需要)
|
||||||
|
public uint UnityCRC; // Unity CRC 校验值
|
||||||
|
|
||||||
|
public Dictionary<string, string> Headers;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### DownloadSimulateRequestArgs(模拟下载参数)
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public struct DownloadSimulateRequestArgs
|
||||||
|
{
|
||||||
|
public string URL; // 标识符
|
||||||
|
public long FileSize; // 模拟文件大小
|
||||||
|
public long DownloadSpeed; // 模拟速度(字节/秒),默认 1MB/s
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 回调数据结构体
|
||||||
|
|
||||||
|
| 结构体 | 用途 | 关键字段 |
|
||||||
|
|--------|------|----------|
|
||||||
|
| `DownloaderFinishData` | 下载完成回调 | `PackageName`, `Succeed` |
|
||||||
|
| `DownloadUpdateData` | 进度更新回调 | `Progress`, `TotalDownloadBytes`, `CurrentDownloadBytes` |
|
||||||
|
| `DownloadErrorData` | 下载错误回调 | `FileName`, `ErrorInfo` |
|
||||||
|
| `DownloadFileData` | 文件完成回调 | `FileName`, `FileSize` |
|
||||||
|
| `ImportFileInfo` | 导入文件元数据 | `FilePath`, `BundleName`, `BundleGUID` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 核心类说明
|
||||||
|
|
||||||
|
### UnityWebRequestBackend
|
||||||
|
|
||||||
|
默认的后端实现,基于 Unity 的 UnityWebRequest API。
|
||||||
|
|
||||||
|
**特性:**
|
||||||
|
- 支持自定义 UnityWebRequest 创建方式(证书验证、代理等)
|
||||||
|
- 无需手动调用 Update(),UnityWebRequest 自动驱动
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 自定义 UnityWebRequest 创建(建议通过 backend 构造函数传入)
|
||||||
|
UnityWebRequestCreator creator = (url, method) =>
|
||||||
|
{
|
||||||
|
var request = new UnityWebRequest(url, method);
|
||||||
|
// 自定义配置...
|
||||||
|
return request;
|
||||||
|
};
|
||||||
|
|
||||||
|
IDownloadBackend backend = new UnityWebRequestBackend(creator);
|
||||||
|
```
|
||||||
|
|
||||||
|
### UnityWebRequestDownloaderBase
|
||||||
|
|
||||||
|
抽象基类,封装所有下载器的通用逻辑。
|
||||||
|
|
||||||
|
**职责:**
|
||||||
|
- 管理请求生命周期和状态转换
|
||||||
|
- 实现看门狗监控机制
|
||||||
|
- 追踪下载进度和字节数
|
||||||
|
- 处理超时和错误
|
||||||
|
|
||||||
|
**生命周期:**
|
||||||
|
|
||||||
|
```
|
||||||
|
None ──► SendRequest() ──► Running ──► PollingRequest() ──┬──► Succeed
|
||||||
|
├──► Failed
|
||||||
|
└──► Aborted
|
||||||
|
```
|
||||||
|
|
||||||
|
### 具体下载器
|
||||||
|
|
||||||
|
| 下载器 | 实现接口 | 使用场景 |
|
||||||
|
|--------|----------|----------|
|
||||||
|
| `UnityWebRequestFileDownloader` | `IDownloadFileRequest` | 大文件下载到本地 |
|
||||||
|
| `UnityWebRequestHeadDownloader` | `IDownloadHeadRequest` | 检查资源信息 |
|
||||||
|
| `UnityWebRequestBytesDownloader` | `IDownloadBytesRequest` | 小文件内存加载 |
|
||||||
|
| `UnityWebRequestTextDownloader` | `IDownloadTextRequest` | 文本文件下载 |
|
||||||
|
| `UnityWebRequestAssetBundleDownloader` | `IDownloadAssetBundleRequest` | AB 包下载加载 |
|
||||||
|
| `VirtualFileDownloader` | `IDownloadFileRequest` | 编辑器模拟下载 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 使用示例
|
||||||
|
|
||||||
|
### 基础文件下载
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 1. 创建后端和请求
|
||||||
|
IDownloadBackend backend = new UnityWebRequestBackend();
|
||||||
|
var args = new DownloadFileRequestArgs(
|
||||||
|
url: "https://example.com/file.zip",
|
||||||
|
savePath: "/path/to/save/file.zip",
|
||||||
|
timeout: 30,
|
||||||
|
watchdogTime: 0);
|
||||||
|
IDownloadFileRequest request = backend.CreateFileRequest(args);
|
||||||
|
|
||||||
|
// 2. 发起并轮询
|
||||||
|
request.SendRequest();
|
||||||
|
while (!request.IsDone)
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
// 可选:显示进度
|
||||||
|
float progress = request.DownloadProgress;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 检查结果
|
||||||
|
if (request.Status == EDownloadRequestStatus.Succeed)
|
||||||
|
{
|
||||||
|
Debug.Log("下载成功");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.LogError($"下载失败: {request.Error}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 清理资源
|
||||||
|
request.Dispose();
|
||||||
|
```
|
||||||
|
|
||||||
|
### 断点续传
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 获取已下载的文件大小
|
||||||
|
long existingFileSize = new FileInfo(savePath).Length;
|
||||||
|
|
||||||
|
var args = new DownloadFileRequestArgs(
|
||||||
|
url: url,
|
||||||
|
savePath: savePath,
|
||||||
|
timeout: 30,
|
||||||
|
watchdogTime: 0,
|
||||||
|
appendToFile: true, // 追加写入
|
||||||
|
removeFileOnAbort: false, // 中止时保留文件
|
||||||
|
resumeFromBytes: existingFileSize); // 断点位置
|
||||||
|
|
||||||
|
IDownloadFileRequest request = backend.CreateFileRequest(args);
|
||||||
|
request.SendRequest();
|
||||||
|
// ... 轮询等待完成
|
||||||
|
```
|
||||||
|
|
||||||
|
### 看门狗保护
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var args = new DownloadFileRequestArgs(
|
||||||
|
url: url,
|
||||||
|
savePath: path,
|
||||||
|
timeout: 30,
|
||||||
|
watchdogTime: 30); // 30秒无数据自动中止
|
||||||
|
|
||||||
|
IDownloadFileRequest request = backend.CreateFileRequest(args);
|
||||||
|
request.SendRequest();
|
||||||
|
|
||||||
|
while (!request.IsDone)
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否因看门狗超时而中止
|
||||||
|
if (request.Status == EDownloadRequestStatus.Aborted)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("下载超时,已自动中止");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### HEAD 请求获取文件信息
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var args = new DownloadDataRequestArgs(
|
||||||
|
url: "https://example.com/file.zip",
|
||||||
|
timeout: 30,
|
||||||
|
watchdogTime: 0);
|
||||||
|
|
||||||
|
IDownloadHeadRequest request = backend.CreateHeadRequest(args);
|
||||||
|
request.SendRequest();
|
||||||
|
|
||||||
|
while (!request.IsDone)
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.Status == EDownloadRequestStatus.Succeed)
|
||||||
|
{
|
||||||
|
long fileSize = request.ContentLength;
|
||||||
|
string etag = request.ETag;
|
||||||
|
string lastModified = request.LastModified;
|
||||||
|
|
||||||
|
Debug.Log($"文件大小: {fileSize}, ETag: {etag}");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 下载字节数据
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var args = new DownloadDataRequestArgs(
|
||||||
|
url: "https://example.com/data.json",
|
||||||
|
timeout: 30,
|
||||||
|
watchdogTime: 0);
|
||||||
|
|
||||||
|
IDownloadBytesRequest request = backend.CreateBytesRequest(args);
|
||||||
|
request.SendRequest();
|
||||||
|
|
||||||
|
while (!request.IsDone)
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.Status == EDownloadRequestStatus.Succeed)
|
||||||
|
{
|
||||||
|
byte[] data = request.Result;
|
||||||
|
// 处理数据...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 设计模式
|
||||||
|
|
||||||
|
### 工厂模式
|
||||||
|
|
||||||
|
`IDownloadBackend` 作为工厂接口,创建各类下载请求对象:
|
||||||
|
|
||||||
|
```
|
||||||
|
IDownloadBackend
|
||||||
|
├── CreateHeadRequest() ──► IDownloadHeadRequest
|
||||||
|
├── CreateFileRequest() ──► IDownloadFileRequest
|
||||||
|
├── CreateBytesRequest() ──► IDownloadBytesRequest
|
||||||
|
├── CreateTextRequest() ──► IDownloadTextRequest
|
||||||
|
├── CreateAssetBundleRequest() ──► IDownloadAssetBundleRequest
|
||||||
|
└── CreateSimulateRequest() ──► IDownloadFileRequest
|
||||||
|
```
|
||||||
|
|
||||||
|
### 策略模式
|
||||||
|
|
||||||
|
通过实现 `IDownloadBackend` 接口,可以替换底层网络库:
|
||||||
|
|
||||||
|
```
|
||||||
|
IDownloadBackend (接口)
|
||||||
|
├── UnityWebRequestBackend (默认实现)
|
||||||
|
├── BestHTTPBackend (可扩展)
|
||||||
|
└── CustomBackend (自定义)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 状态机模式
|
||||||
|
|
||||||
|
请求生命周期通过状态机管理:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────┐ SendRequest() ┌─────────┐
|
||||||
|
│ None │ ──────────────────► │ Running │
|
||||||
|
└──────┘ └────┬────┘
|
||||||
|
│ PollingRequest()
|
||||||
|
┌─────────────┼─────────────┐
|
||||||
|
▼ ▼ ▼
|
||||||
|
┌─────────┐ ┌──────────┐ ┌─────────┐
|
||||||
|
│ Succeed │ │ Failed │ │ Aborted │
|
||||||
|
└─────────┘ └──────────┘ └─────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 看门狗模式
|
||||||
|
|
||||||
|
监控数据接收,防止网络卡顿导致请求无限等待:
|
||||||
|
|
||||||
|
```
|
||||||
|
每帧轮询 PollingRequest()
|
||||||
|
│
|
||||||
|
├── 收到新数据 ──► 重置计时器
|
||||||
|
│
|
||||||
|
└── 未收到数据 ──► 计时器累加
|
||||||
|
│
|
||||||
|
└── 超过 WatchdogTime ──► AbortRequest()
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 类继承关系
|
||||||
|
|
||||||
|
```
|
||||||
|
IDownloadRequest (基础接口)
|
||||||
|
│
|
||||||
|
├── IDownloadHeadRequest (HEAD 请求)
|
||||||
|
├── IDownloadFileRequest (文件下载)
|
||||||
|
├── IDownloadBytesRequest (字节下载)
|
||||||
|
├── IDownloadTextRequest (文本下载)
|
||||||
|
└── IDownloadAssetBundleRequest (AssetBundle 下载)
|
||||||
|
|
||||||
|
UnityWebRequestDownloaderBase (抽象基类)
|
||||||
|
│
|
||||||
|
├── UnityWebRequestFileDownloader ──► IDownloadFileRequest
|
||||||
|
├── UnityWebRequestHeadDownloader ──► IDownloadHeadRequest
|
||||||
|
├── UnityWebRequestBytesDownloader ──► IDownloadBytesRequest
|
||||||
|
├── UnityWebRequestTextDownloader ──► IDownloadTextRequest
|
||||||
|
└── UnityWebRequestAssetBundleDownloader ──► IDownloadAssetBundleRequest
|
||||||
|
|
||||||
|
VirtualFileDownloader (独立实现) ──► IDownloadFileRequest
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 工具类
|
||||||
|
|
||||||
|
### DownloadSystemHelper
|
||||||
|
|
||||||
|
提供跨平台的工具函数:
|
||||||
|
|
||||||
|
| 方法 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `ConvertToWWWPath()` | 转换本地路径为 WWW 协议 URL |
|
||||||
|
| `IsRequestLocalFile()` | 判断是否本地文件请求 |
|
||||||
|
|
||||||
|
### WebRequestCounter
|
||||||
|
|
||||||
|
请求失败计数器,用于诊断统计:
|
||||||
|
|
||||||
|
- 线程安全:内部使用 `Dictionary` 且未加锁,约定只在主线程调用;如需多线程统计请在外层加锁或改造实现
|
||||||
|
- Key 规则:`$"{packageName}_{eventName}"`
|
||||||
|
- 统计口径:**仅统计网络请求失败**(`IDownloadRequest.Status != Succeed` 时记录),不统计内容为空、校验失败、解析失败等业务层失败
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 记录失败
|
||||||
|
WebRequestCounter.RecordRequestFailed(packageName, eventName);
|
||||||
|
|
||||||
|
// 查询失败次数
|
||||||
|
int count = WebRequestCounter.GetRequestFailedCount(packageName, eventName);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **资源释放**:使用完毕后务必调用 `Dispose()` 释放资源
|
||||||
|
- `AbortRequest()` 仅用于中止请求与切换状态,不等同于释放资源;无论成功/失败/中止都需要 `Dispose()`
|
||||||
|
- 推荐使用 `try/finally` 确保释放(尤其是上层可能提前中止的场景)
|
||||||
|
2. **断点续传**:需要服务器支持 `Range` 请求头和 `206 Partial Content` 响应
|
||||||
|
- 若服务端不支持 Range 仍返回 200,全量内容可能会被追加写入,导致文件损坏
|
||||||
|
3. **看门狗超时**:设置为 0 表示禁用,建议根据网络环境设置合理值
|
||||||
|
4. **内存下载**:`IDownloadBytesRequest` 会将整个响应体加载到内存,不适合大文件
|
||||||
|
5. **驱动更新**:部分第三方网络库实现的 backend 可能需要每帧调用 `IDownloadBackend.Update()` 进行驱动
|
||||||
|
6. **中止语义**:`Aborted` 可能来自用户主动 `AbortRequest()` 或看门狗超时;中止场景下 `HttpCode/Error` 可能为默认值(例如 0/空)
|
||||||
|
7. **线程安全**:所有下载请求的创建和轮询应在主线程进行
|
||||||
7
Assets/YooAsset/Runtime/DownloadSystem/README.md.meta
Normal file
7
Assets/YooAsset/Runtime/DownloadSystem/README.md.meta
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 2c65d3ba8da65004b8d36dbeecc6c6be
|
||||||
|
TextScriptImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -1,9 +1,14 @@
|
|||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace YooAsset
|
namespace YooAsset
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 网络请求失败计数器(诊断用)
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 线程安全:内部使用 Dictionary 且未加锁,约定只在 Unity 主线程调用。
|
||||||
|
/// 如需在多线程/回调线程调用,请在外层加锁或改为并发容器实现。
|
||||||
|
/// </remarks>
|
||||||
internal class WebRequestCounter
|
internal class WebRequestCounter
|
||||||
{
|
{
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
@@ -15,12 +20,12 @@ namespace YooAsset
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 记录网络请求失败事件的次数
|
/// 失败计数记录表(key = $"{packageName}_{eventName}")
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static readonly Dictionary<string, int> _requestFailedRecorder = new Dictionary<string, int>(1000);
|
private static readonly Dictionary<string, int> _requestFailedRecorder = new Dictionary<string, int>(1000);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 记录请求失败事件
|
/// 记录一次失败
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static void RecordRequestFailed(string packageName, string eventName)
|
public static void RecordRequestFailed(string packageName, string eventName)
|
||||||
{
|
{
|
||||||
@@ -31,14 +36,14 @@ namespace YooAsset
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取请求失败的次数
|
/// 获取失败次数
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static int GetRequestFailedCount(string packageName, string eventName)
|
public static int GetRequestFailedCount(string packageName, string eventName)
|
||||||
{
|
{
|
||||||
string key = $"{packageName}_{eventName}";
|
string key = $"{packageName}_{eventName}";
|
||||||
if (_requestFailedRecorder.ContainsKey(key) == false)
|
if (_requestFailedRecorder.TryGetValue(key, out int count))
|
||||||
_requestFailedRecorder.Add(key, 0);
|
return count;
|
||||||
return _requestFailedRecorder[key];
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -205,6 +205,9 @@ namespace YooAsset
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static DefaultBuildinFileCatalog DeserializeFromBinary(byte[] binaryData)
|
public static DefaultBuildinFileCatalog DeserializeFromBinary(byte[] binaryData)
|
||||||
{
|
{
|
||||||
|
if (binaryData == null || binaryData.Length == 0)
|
||||||
|
throw new Exception("Catalog file data is null or empty !");
|
||||||
|
|
||||||
// 创建缓存器
|
// 创建缓存器
|
||||||
BufferReader buffer = new BufferReader(binaryData);
|
BufferReader buffer = new BufferReader(binaryData);
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,11 @@ namespace YooAsset
|
|||||||
protected IFileSystem _unpackFileSystem;
|
protected IFileSystem _unpackFileSystem;
|
||||||
protected string _packageRoot;
|
protected string _packageRoot;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 下载后台接口
|
||||||
|
/// </summary>
|
||||||
|
public IDownloadBackend DownloadBackend { private set; get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 包裹名称
|
/// 包裹名称
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -53,6 +58,11 @@ namespace YooAsset
|
|||||||
}
|
}
|
||||||
|
|
||||||
#region 自定义参数
|
#region 自定义参数
|
||||||
|
/// <summary>
|
||||||
|
/// 自定义参数:UnityWebRequest 创建委托
|
||||||
|
/// </summary>
|
||||||
|
public UnityWebRequestCreator WebRequestCreator { private set; get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 自定义参数:覆盖安装缓存清理模式
|
/// 自定义参数:覆盖安装缓存清理模式
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -66,7 +76,7 @@ namespace YooAsset
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 自定义参数:初始化的时候缓存文件校验最大并发数
|
/// 自定义参数:初始化的时候缓存文件校验最大并发数
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int FileVerifyMaxConcurrency { private set; get; } = int.MaxValue;
|
public int FileVerifyMaxConcurrency { private set; get; } = 32;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 自定义参数:数据文件追加文件格式
|
/// 自定义参数:数据文件追加文件格式
|
||||||
@@ -156,6 +166,13 @@ namespace YooAsset
|
|||||||
var operation = new DBFSLoadRawBundleOperation(this, bundle);
|
var operation = new DBFSLoadRawBundleOperation(this, bundle);
|
||||||
return operation;
|
return operation;
|
||||||
}
|
}
|
||||||
|
#if TUANJIE_1_7_OR_NEWER
|
||||||
|
else if (bundle.BundleType == (int)EBuildBundleType.InstantBundle)
|
||||||
|
{
|
||||||
|
var operation = new DBFSLoadInstantBundleOperation(this, bundle);
|
||||||
|
return operation;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
string error = $"{nameof(DefaultBuildinFileSystem)} not support load bundle type : {bundle.BundleType}";
|
string error = $"{nameof(DefaultBuildinFileSystem)} not support load bundle type : {bundle.BundleType}";
|
||||||
@@ -166,7 +183,15 @@ namespace YooAsset
|
|||||||
|
|
||||||
public virtual void SetParameter(string name, object value)
|
public virtual void SetParameter(string name, object value)
|
||||||
{
|
{
|
||||||
if (name == FileSystemParametersDefine.INSTALL_CLEAR_MODE)
|
if (name == FileSystemParametersDefine.DOWNLOAD_BACKEND)
|
||||||
|
{
|
||||||
|
DownloadBackend = (IDownloadBackend)value;
|
||||||
|
}
|
||||||
|
else if (name == FileSystemParametersDefine.UNITY_WEB_REQUEST_CREATOR)
|
||||||
|
{
|
||||||
|
WebRequestCreator = (UnityWebRequestCreator)value;
|
||||||
|
}
|
||||||
|
else if (name == FileSystemParametersDefine.INSTALL_CLEAR_MODE)
|
||||||
{
|
{
|
||||||
InstallClearMode = (EOverwriteInstallClearMode)value;
|
InstallClearMode = (EOverwriteInstallClearMode)value;
|
||||||
}
|
}
|
||||||
@@ -225,10 +250,16 @@ namespace YooAsset
|
|||||||
else
|
else
|
||||||
_packageRoot = packageRoot;
|
_packageRoot = packageRoot;
|
||||||
|
|
||||||
|
// 创建默认的下载后台接口
|
||||||
|
if (DownloadBackend == null)
|
||||||
|
DownloadBackend = new UnityWebRequestBackend(WebRequestCreator);
|
||||||
|
|
||||||
// 创建解压文件系统
|
// 创建解压文件系统
|
||||||
var remoteServices = new DefaultUnpackRemoteServices(_packageRoot);
|
var remoteServices = new DefaultUnpackRemoteServices(_packageRoot);
|
||||||
_unpackFileSystem = new DefaultUnpackFileSystem();
|
_unpackFileSystem = new DefaultUnpackFileSystem();
|
||||||
_unpackFileSystem.SetParameter(FileSystemParametersDefine.REMOTE_SERVICES, remoteServices);
|
_unpackFileSystem.SetParameter(FileSystemParametersDefine.REMOTE_SERVICES, remoteServices);
|
||||||
|
_unpackFileSystem.SetParameter(FileSystemParametersDefine.DOWNLOAD_BACKEND, DownloadBackend);
|
||||||
|
_unpackFileSystem.SetParameter(FileSystemParametersDefine.UNITY_WEB_REQUEST_CREATOR, WebRequestCreator);
|
||||||
_unpackFileSystem.SetParameter(FileSystemParametersDefine.INSTALL_CLEAR_MODE, InstallClearMode);
|
_unpackFileSystem.SetParameter(FileSystemParametersDefine.INSTALL_CLEAR_MODE, InstallClearMode);
|
||||||
_unpackFileSystem.SetParameter(FileSystemParametersDefine.FILE_VERIFY_LEVEL, FileVerifyLevel);
|
_unpackFileSystem.SetParameter(FileSystemParametersDefine.FILE_VERIFY_LEVEL, FileVerifyLevel);
|
||||||
_unpackFileSystem.SetParameter(FileSystemParametersDefine.FILE_VERIFY_MAX_CONCURRENCY, FileVerifyMaxConcurrency);
|
_unpackFileSystem.SetParameter(FileSystemParametersDefine.FILE_VERIFY_MAX_CONCURRENCY, FileVerifyMaxConcurrency);
|
||||||
@@ -239,6 +270,17 @@ namespace YooAsset
|
|||||||
}
|
}
|
||||||
public virtual void OnDestroy()
|
public virtual void OnDestroy()
|
||||||
{
|
{
|
||||||
|
if (_unpackFileSystem != null)
|
||||||
|
{
|
||||||
|
_unpackFileSystem.OnDestroy();
|
||||||
|
_unpackFileSystem = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DownloadBackend != null)
|
||||||
|
{
|
||||||
|
DownloadBackend.Dispose();
|
||||||
|
DownloadBackend = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual bool Belong(PackageBundle bundle)
|
public virtual bool Belong(PackageBundle bundle)
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ namespace YooAsset
|
|||||||
string packageVersion = _requestBuildinPackageVersionOp.PackageVersion;
|
string packageVersion = _requestBuildinPackageVersionOp.PackageVersion;
|
||||||
string destFilePath = GetCopyPackageHashDestPath(packageVersion);
|
string destFilePath = GetCopyPackageHashDestPath(packageVersion);
|
||||||
string sourceFilePath = _fileSystem.GetBuildinPackageHashFilePath(packageVersion);
|
string sourceFilePath = _fileSystem.GetBuildinPackageHashFilePath(packageVersion);
|
||||||
_copyBuildinHashFileOp = new CopyBuildinFileOperation(sourceFilePath, destFilePath);
|
_copyBuildinHashFileOp = new CopyBuildinFileOperation(_fileSystem, sourceFilePath, destFilePath);
|
||||||
_copyBuildinHashFileOp.StartOperation();
|
_copyBuildinHashFileOp.StartOperation();
|
||||||
AddChildOperation(_copyBuildinHashFileOp);
|
AddChildOperation(_copyBuildinHashFileOp);
|
||||||
}
|
}
|
||||||
@@ -106,7 +106,7 @@ namespace YooAsset
|
|||||||
string packageVersion = _requestBuildinPackageVersionOp.PackageVersion;
|
string packageVersion = _requestBuildinPackageVersionOp.PackageVersion;
|
||||||
string destFilePath = GetCopyPackageManifestDestPath(packageVersion);
|
string destFilePath = GetCopyPackageManifestDestPath(packageVersion);
|
||||||
string sourceFilePath = _fileSystem.GetBuildinPackageManifestFilePath(packageVersion);
|
string sourceFilePath = _fileSystem.GetBuildinPackageManifestFilePath(packageVersion);
|
||||||
_copyBuildinManifestFileOp = new CopyBuildinFileOperation(sourceFilePath, destFilePath);
|
_copyBuildinManifestFileOp = new CopyBuildinFileOperation(_fileSystem, sourceFilePath, destFilePath);
|
||||||
_copyBuildinManifestFileOp.StartOperation();
|
_copyBuildinManifestFileOp.StartOperation();
|
||||||
AddChildOperation(_copyBuildinManifestFileOp);
|
AddChildOperation(_copyBuildinManifestFileOp);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,13 +14,15 @@ namespace YooAsset
|
|||||||
Done,
|
Done,
|
||||||
}
|
}
|
||||||
|
|
||||||
private UnityWebFileRequestOperation _webFileRequestOp;
|
private readonly DefaultBuildinFileSystem _fileSystem;
|
||||||
private readonly string _sourceFilePath;
|
private readonly string _sourceFilePath;
|
||||||
private readonly string _destFilePath;
|
private readonly string _destFilePath;
|
||||||
|
private IDownloadFileRequest _webFileRequestOp;
|
||||||
private ESteps _steps = ESteps.None;
|
private ESteps _steps = ESteps.None;
|
||||||
|
|
||||||
public CopyBuildinFileOperation(string sourceFilePath, string destFilePath)
|
public CopyBuildinFileOperation(DefaultBuildinFileSystem fileSystem, string sourceFilePath, string destFilePath)
|
||||||
{
|
{
|
||||||
|
_fileSystem = fileSystem;
|
||||||
_sourceFilePath = sourceFilePath;
|
_sourceFilePath = sourceFilePath;
|
||||||
_destFilePath = destFilePath;
|
_destFilePath = destFilePath;
|
||||||
}
|
}
|
||||||
@@ -76,16 +78,15 @@ namespace YooAsset
|
|||||||
if (_webFileRequestOp == null)
|
if (_webFileRequestOp == null)
|
||||||
{
|
{
|
||||||
string url = DownloadSystemHelper.ConvertToWWWPath(_sourceFilePath);
|
string url = DownloadSystemHelper.ConvertToWWWPath(_sourceFilePath);
|
||||||
_webFileRequestOp = new UnityWebFileRequestOperation(url, _destFilePath, 60);
|
var args = new DownloadFileRequestArgs(url, _destFilePath, 60, 0);
|
||||||
_webFileRequestOp.StartOperation();
|
_webFileRequestOp = _fileSystem.DownloadBackend.CreateFileRequest(args);
|
||||||
AddChildOperation(_webFileRequestOp);
|
_webFileRequestOp.SendRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
_webFileRequestOp.UpdateOperation();
|
|
||||||
if (_webFileRequestOp.IsDone == false)
|
if (_webFileRequestOp.IsDone == false)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (_webFileRequestOp.Status == EOperationStatus.Succeed)
|
if (_webFileRequestOp.Status == EDownloadRequestStatus.Succeed)
|
||||||
{
|
{
|
||||||
_steps = ESteps.Done;
|
_steps = ESteps.Done;
|
||||||
Status = EOperationStatus.Succeed;
|
Status = EOperationStatus.Succeed;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace YooAsset
|
|||||||
}
|
}
|
||||||
|
|
||||||
private readonly DefaultBuildinFileSystem _fileSystem;
|
private readonly DefaultBuildinFileSystem _fileSystem;
|
||||||
private UnityWebDataRequestOperation _webDataRequestOp;
|
private IDownloadBytesRequest _webDataRequestOp;
|
||||||
private byte[] _fileData;
|
private byte[] _fileData;
|
||||||
private ESteps _steps = ESteps.None;
|
private ESteps _steps = ESteps.None;
|
||||||
|
|
||||||
@@ -57,16 +57,15 @@ namespace YooAsset
|
|||||||
{
|
{
|
||||||
string filePath = _fileSystem.GetCatalogBinaryFileLoadPath();
|
string filePath = _fileSystem.GetCatalogBinaryFileLoadPath();
|
||||||
string url = DownloadSystemHelper.ConvertToWWWPath(filePath);
|
string url = DownloadSystemHelper.ConvertToWWWPath(filePath);
|
||||||
_webDataRequestOp = new UnityWebDataRequestOperation(url, 60);
|
var args = new DownloadDataRequestArgs(url, 60, 0);
|
||||||
_webDataRequestOp.StartOperation();
|
_webDataRequestOp = _fileSystem.DownloadBackend.CreateBytesRequest(args);
|
||||||
AddChildOperation(_webDataRequestOp);
|
_webDataRequestOp.SendRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
_webDataRequestOp.UpdateOperation();
|
|
||||||
if (_webDataRequestOp.IsDone == false)
|
if (_webDataRequestOp.IsDone == false)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (_webDataRequestOp.Status == EOperationStatus.Succeed)
|
if (_webDataRequestOp.Status == EDownloadRequestStatus.Succeed)
|
||||||
{
|
{
|
||||||
_fileData = _webDataRequestOp.Result;
|
_fileData = _webDataRequestOp.Result;
|
||||||
_steps = ESteps.LoadCatalog;
|
_steps = ESteps.LoadCatalog;
|
||||||
@@ -87,11 +86,11 @@ namespace YooAsset
|
|||||||
_steps = ESteps.Done;
|
_steps = ESteps.Done;
|
||||||
Status = EOperationStatus.Succeed;
|
Status = EOperationStatus.Succeed;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_steps = ESteps.Done;
|
_steps = ESteps.Done;
|
||||||
Status = EOperationStatus.Failed;
|
Status = EOperationStatus.Failed;
|
||||||
Error = $"Failed to load catalog file : {e.Message}";
|
Error = $"Failed to load catalog file : {ex.Message}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ namespace YooAsset
|
|||||||
private readonly DefaultBuildinFileSystem _fileSystem;
|
private readonly DefaultBuildinFileSystem _fileSystem;
|
||||||
private readonly string _packageVersion;
|
private readonly string _packageVersion;
|
||||||
private readonly string _packageHash;
|
private readonly string _packageHash;
|
||||||
private UnityWebDataRequestOperation _webDataRequestOp;
|
private IDownloadBytesRequest _webDataRequestOp;
|
||||||
private DeserializeManifestOperation _deserializer;
|
private DeserializeManifestOperation _deserializer;
|
||||||
private byte[] _fileData;
|
private byte[] _fileData;
|
||||||
private ESteps _steps = ESteps.None;
|
private ESteps _steps = ESteps.None;
|
||||||
@@ -63,16 +63,15 @@ namespace YooAsset
|
|||||||
{
|
{
|
||||||
string filePath = _fileSystem.GetBuildinPackageManifestFilePath(_packageVersion);
|
string filePath = _fileSystem.GetBuildinPackageManifestFilePath(_packageVersion);
|
||||||
string url = DownloadSystemHelper.ConvertToWWWPath(filePath);
|
string url = DownloadSystemHelper.ConvertToWWWPath(filePath);
|
||||||
_webDataRequestOp = new UnityWebDataRequestOperation(url, 60);
|
var args = new DownloadDataRequestArgs(url, 60, 0);
|
||||||
_webDataRequestOp.StartOperation();
|
_webDataRequestOp = _fileSystem.DownloadBackend.CreateBytesRequest(args);
|
||||||
AddChildOperation(_webDataRequestOp);
|
_webDataRequestOp.SendRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
_webDataRequestOp.UpdateOperation();
|
|
||||||
if (_webDataRequestOp.IsDone == false)
|
if (_webDataRequestOp.IsDone == false)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (_webDataRequestOp.Status == EOperationStatus.Succeed)
|
if (_webDataRequestOp.Status == EDownloadRequestStatus.Succeed)
|
||||||
{
|
{
|
||||||
_fileData = _webDataRequestOp.Result;
|
_fileData = _webDataRequestOp.Result;
|
||||||
_steps = ESteps.VerifyFileData;
|
_steps = ESteps.VerifyFileData;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace YooAsset
|
|||||||
|
|
||||||
private readonly DefaultBuildinFileSystem _fileSystem;
|
private readonly DefaultBuildinFileSystem _fileSystem;
|
||||||
private readonly string _packageVersion;
|
private readonly string _packageVersion;
|
||||||
private UnityWebTextRequestOperation _webTextRequestOp;
|
private IDownloadTextRequest _webTextRequestOp;
|
||||||
private ESteps _steps = ESteps.None;
|
private ESteps _steps = ESteps.None;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -58,16 +58,15 @@ namespace YooAsset
|
|||||||
{
|
{
|
||||||
string filePath = _fileSystem.GetBuildinPackageHashFilePath(_packageVersion);
|
string filePath = _fileSystem.GetBuildinPackageHashFilePath(_packageVersion);
|
||||||
string url = DownloadSystemHelper.ConvertToWWWPath(filePath);
|
string url = DownloadSystemHelper.ConvertToWWWPath(filePath);
|
||||||
_webTextRequestOp = new UnityWebTextRequestOperation(url, 60);
|
var args = new DownloadDataRequestArgs(url, 60, 0);
|
||||||
_webTextRequestOp.StartOperation();
|
_webTextRequestOp = _fileSystem.DownloadBackend.CreateTextRequest(args);
|
||||||
AddChildOperation(_webTextRequestOp);
|
_webTextRequestOp.SendRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
_webTextRequestOp.UpdateOperation();
|
|
||||||
if (_webTextRequestOp.IsDone == false)
|
if (_webTextRequestOp.IsDone == false)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (_webTextRequestOp.Status == EOperationStatus.Succeed)
|
if (_webTextRequestOp.Status == EDownloadRequestStatus.Succeed)
|
||||||
{
|
{
|
||||||
PackageHash = _webTextRequestOp.Result;
|
PackageHash = _webTextRequestOp.Result;
|
||||||
_steps = ESteps.CheckResult;
|
_steps = ESteps.CheckResult;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ namespace YooAsset
|
|||||||
}
|
}
|
||||||
|
|
||||||
private readonly DefaultBuildinFileSystem _fileSystem;
|
private readonly DefaultBuildinFileSystem _fileSystem;
|
||||||
private UnityWebTextRequestOperation _webTextRequestOp;
|
private IDownloadTextRequest _webTextRequestOp;
|
||||||
private ESteps _steps = ESteps.None;
|
private ESteps _steps = ESteps.None;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -56,16 +56,15 @@ namespace YooAsset
|
|||||||
{
|
{
|
||||||
string filePath = _fileSystem.GetBuildinPackageVersionFilePath();
|
string filePath = _fileSystem.GetBuildinPackageVersionFilePath();
|
||||||
string url = DownloadSystemHelper.ConvertToWWWPath(filePath);
|
string url = DownloadSystemHelper.ConvertToWWWPath(filePath);
|
||||||
_webTextRequestOp = new UnityWebTextRequestOperation(url, 60);
|
var args = new DownloadDataRequestArgs(url, 60, 0);
|
||||||
_webTextRequestOp.StartOperation();
|
_webTextRequestOp = _fileSystem.DownloadBackend.CreateTextRequest(args);
|
||||||
AddChildOperation(_webTextRequestOp);
|
_webTextRequestOp.SendRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
_webTextRequestOp.UpdateOperation();
|
|
||||||
if (_webTextRequestOp.IsDone == false)
|
if (_webTextRequestOp.IsDone == false)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (_webTextRequestOp.Status == EOperationStatus.Succeed)
|
if (_webTextRequestOp.Status == EDownloadRequestStatus.Succeed)
|
||||||
{
|
{
|
||||||
PackageVersion = _webTextRequestOp.Result;
|
PackageVersion = _webTextRequestOp.Result;
|
||||||
_steps = ESteps.CheckResult;
|
_steps = ESteps.CheckResult;
|
||||||
|
|||||||
@@ -0,0 +1,488 @@
|
|||||||
|
# DefaultBuildinFileSystem 内置文件系统
|
||||||
|
|
||||||
|
## 模块概述
|
||||||
|
|
||||||
|
DefaultBuildinFileSystem 是 YooAsset 的**内置资源文件系统**,用于管理打包到应用程序中的资源文件(StreamingAssets)。该文件系统支持 AssetBundle 和原生文件的加载,并内置解压文件系统以处理 Android/OpenHarmony 平台的特殊需求。
|
||||||
|
|
||||||
|
### 核心特性
|
||||||
|
|
||||||
|
- **内置资源管理**:管理 StreamingAssets 目录下的资源文件
|
||||||
|
- **Catalog 目录系统**:使用目录文件快速查询内置资源
|
||||||
|
- **自动解压机制**:Android/OpenHarmony 平台自动解压加密和原生文件
|
||||||
|
- **清单拷贝功能**:支持将内置清单拷贝到沙盒目录
|
||||||
|
- **加密资源支持**:通过解密服务接口支持加密资源加载
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 设计目标
|
||||||
|
|
||||||
|
| 目标 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| **跨平台支持** | 统一处理各平台 StreamingAssets 的访问差异 |
|
||||||
|
| **高效查询** | 通过 Catalog 文件快速判断资源是否内置 |
|
||||||
|
| **自动解压** | 自动处理 Android 平台无法直接访问的资源 |
|
||||||
|
| **灵活配置** | 支持多种参数配置适应不同需求 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 文件结构
|
||||||
|
|
||||||
|
```
|
||||||
|
DefaultBuildinFileSystem/
|
||||||
|
├── DefaultBuildinFileSystem.cs # 文件系统主类
|
||||||
|
├── DefaultBuildinFileSystemDefine.cs # 常量定义
|
||||||
|
├── DefaultBuildinFileCatalog.cs # 内置资源目录结构
|
||||||
|
├── CatalogDefine.cs # Catalog 文件格式定义
|
||||||
|
├── CatalogTools.cs # Catalog 序列化工具
|
||||||
|
└── Operation/ # 操作类
|
||||||
|
├── DBFSInitializeOperation.cs # 初始化操作
|
||||||
|
├── DBFSRequestPackageVersionOperation.cs # 请求版本操作
|
||||||
|
├── DBFSLoadPackageManifestOperation.cs # 加载清单操作
|
||||||
|
├── DBFSLoadBundleOperation.cs # 加载资源包操作
|
||||||
|
└── internal/ # 内部操作类
|
||||||
|
├── CopyBuildinFileOperation.cs # 拷贝内置文件操作
|
||||||
|
├── LoadBuildinCatalogFileOperation.cs # 加载 Catalog 文件操作
|
||||||
|
├── LoadBuildinPackageManifestOperation.cs# 加载清单文件操作
|
||||||
|
├── RequestBuildinPackageHashOperation.cs # 请求哈希文件操作
|
||||||
|
└── RequestBuildinPackageVersionOperation.cs # 请求版本文件操作
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 核心类说明
|
||||||
|
|
||||||
|
### DefaultBuildinFileSystem
|
||||||
|
|
||||||
|
内置文件系统的主类,实现 `IFileSystem` 接口。
|
||||||
|
|
||||||
|
#### 基本属性
|
||||||
|
|
||||||
|
| 属性 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `PackageName` | `string` | 包裹名称 |
|
||||||
|
| `FileRoot` | `string` | 文件根目录(StreamingAssets 下的包裹目录) |
|
||||||
|
| `FileCount` | `int` | 已记录的内置文件数量 |
|
||||||
|
| `DownloadBackend` | `IDownloadBackend` | 下载后台接口 |
|
||||||
|
|
||||||
|
#### 自定义参数
|
||||||
|
|
||||||
|
| 参数 | 类型 | 默认值 | 说明 |
|
||||||
|
|------|------|--------|------|
|
||||||
|
| `InstallClearMode` | `EOverwriteInstallClearMode` | `ClearAllManifestFiles` | 覆盖安装时的缓存清理模式 |
|
||||||
|
| `FileVerifyLevel` | `EFileVerifyLevel` | `Middle` | 文件校验级别 |
|
||||||
|
| `FileVerifyMaxConcurrency` | `int` | `32` | 文件校验最大并发数 |
|
||||||
|
| `AppendFileExtension` | `bool` | `false` | 是否追加文件扩展名 |
|
||||||
|
| `DisableCatalogFile` | `bool` | `false` | 禁用 Catalog 目录文件 |
|
||||||
|
| `CopyBuildinPackageManifest` | `bool` | `false` | 是否拷贝内置清单到沙盒 |
|
||||||
|
| `CopyBuildinPackageManifestDestRoot` | `string` | `null` | 清单拷贝目标目录 |
|
||||||
|
| `UnpackFileSystemRoot` | `string` | `null` | 解压文件系统根目录 |
|
||||||
|
| `DecryptionServices` | `IDecryptionServices` | `null` | 解密服务接口 |
|
||||||
|
| `ManifestServices` | `IManifestRestoreServices` | `null` | 清单恢复服务接口 |
|
||||||
|
| `CopyLocalFileServices` | `ICopyLocalFileServices` | `null` | 本地文件拷贝服务接口 |
|
||||||
|
|
||||||
|
#### 核心方法
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 生命周期
|
||||||
|
void OnCreate(string packageName, string packageRoot);
|
||||||
|
void OnDestroy();
|
||||||
|
void SetParameter(string name, object value);
|
||||||
|
|
||||||
|
// 异步操作
|
||||||
|
FSInitializeFileSystemOperation InitializeFileSystemAsync();
|
||||||
|
FSRequestPackageVersionOperation RequestPackageVersionAsync(bool appendTimeTicks, int timeout);
|
||||||
|
FSLoadPackageManifestOperation LoadPackageManifestAsync(string packageVersion, int timeout);
|
||||||
|
FSLoadBundleOperation LoadBundleFile(PackageBundle bundle);
|
||||||
|
FSDownloadFileOperation DownloadFileAsync(PackageBundle bundle, DownloadFileOptions options);
|
||||||
|
FSClearCacheFilesOperation ClearCacheFilesAsync(PackageManifest manifest, ClearCacheFilesOptions options);
|
||||||
|
|
||||||
|
// 文件查询
|
||||||
|
bool Belong(PackageBundle bundle); // 检查是否属于内置文件
|
||||||
|
bool Exists(PackageBundle bundle); // 检查文件是否存在
|
||||||
|
bool NeedDownload(PackageBundle bundle);// 始终返回 false
|
||||||
|
bool NeedUnpack(PackageBundle bundle); // 检查是否需要解压
|
||||||
|
bool NeedImport(PackageBundle bundle); // 始终返回 false
|
||||||
|
|
||||||
|
// 文件访问
|
||||||
|
string GetBundleFilePath(PackageBundle bundle);
|
||||||
|
byte[] ReadBundleFileData(PackageBundle bundle);
|
||||||
|
string ReadBundleFileText(PackageBundle bundle);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Catalog 目录系统
|
||||||
|
|
||||||
|
### DefaultBuildinFileCatalog
|
||||||
|
|
||||||
|
内置资源目录结构,记录所有内置资源文件的信息。
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
[Serializable]
|
||||||
|
internal class DefaultBuildinFileCatalog
|
||||||
|
{
|
||||||
|
[Serializable]
|
||||||
|
public class FileWrapper
|
||||||
|
{
|
||||||
|
public string BundleGUID; // 资源包 GUID
|
||||||
|
public string FileName; // 文件名
|
||||||
|
}
|
||||||
|
|
||||||
|
public string FileVersion; // 文件版本
|
||||||
|
public string PackageName; // 包裹名称
|
||||||
|
public string PackageVersion; // 包裹版本
|
||||||
|
public List<FileWrapper> Wrappers; // 文件列表
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### CatalogDefine
|
||||||
|
|
||||||
|
Catalog 文件格式常量定义。
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
internal class CatalogDefine
|
||||||
|
{
|
||||||
|
public const int FileMaxSize = 104857600; // 文件极限大小(100MB)
|
||||||
|
public const uint FileSign = 0x133C5EE; // 文件头标记
|
||||||
|
public const string FileVersion = "1.0.0"; // 文件格式版本
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### CatalogTools
|
||||||
|
|
||||||
|
Catalog 文件的序列化和反序列化工具。
|
||||||
|
|
||||||
|
| 方法 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `CreateCatalogFile()` | 生成包裹的内置资源目录文件(编辑器) |
|
||||||
|
| `CreateEmptyCatalogFile()` | 生成空的内置资源目录文件(编辑器) |
|
||||||
|
| `SerializeToJson()` | 序列化为 JSON 文件 |
|
||||||
|
| `DeserializeFromJson()` | 从 JSON 文件反序列化 |
|
||||||
|
| `SerializeToBinary()` | 序列化为二进制文件 |
|
||||||
|
| `DeserializeFromBinary()` | 从二进制文件反序列化 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 操作类说明
|
||||||
|
|
||||||
|
### DBFSInitializeOperation
|
||||||
|
|
||||||
|
初始化操作,执行以下步骤:
|
||||||
|
|
||||||
|
```
|
||||||
|
状态流程:
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ CopyBuildinPackageManifest = true ? │
|
||||||
|
│ │ │
|
||||||
|
│ ├── Yes ──► LoadBuildinPackageVersion │
|
||||||
|
│ │ └── RequestBuildinPackageVersionOp │
|
||||||
|
│ │ ↓ │
|
||||||
|
│ │ CopyBuildinPackageHash │
|
||||||
|
│ │ └── CopyBuildinFileOperation │
|
||||||
|
│ │ ↓ │
|
||||||
|
│ │ CopyBuildinPackageManifest │
|
||||||
|
│ │ └── CopyBuildinFileOperation │
|
||||||
|
│ │ ↓ │
|
||||||
|
│ └── No ─────────────────┘ │
|
||||||
|
│ ↓ │
|
||||||
|
│ InitUnpackFileSystem │
|
||||||
|
│ └── DefaultUnpackFileSystem.Init │
|
||||||
|
│ ↓ │
|
||||||
|
│ DisableCatalogFile = true ? │
|
||||||
|
│ ├── Yes ──► Done (Succeed) │
|
||||||
|
│ └── No ──► LoadCatalogFile │
|
||||||
|
│ └── LoadBuildinCatalog │
|
||||||
|
│ ↓ │
|
||||||
|
│ RecordCatalogFile │
|
||||||
|
│ ↓ │
|
||||||
|
│ Done (Succeed) │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### DBFSLoadBundleOperation
|
||||||
|
|
||||||
|
加载资源包操作,支持多种资源类型。
|
||||||
|
|
||||||
|
#### DBFSLoadAssetBundleOperation
|
||||||
|
|
||||||
|
加载 AssetBundle 文件。
|
||||||
|
|
||||||
|
```
|
||||||
|
状态流程:
|
||||||
|
LoadAssetBundle
|
||||||
|
├── 加密资源 ──► DecryptionServices.LoadAssetBundle[Async]
|
||||||
|
└── 普通资源 ──► AssetBundle.LoadFromFile[Async]
|
||||||
|
↓
|
||||||
|
CheckResult
|
||||||
|
├── 成功 ──► AssetBundleResult
|
||||||
|
└── 失败 ──► Error
|
||||||
|
```
|
||||||
|
|
||||||
|
#### DBFSLoadRawBundleOperation
|
||||||
|
|
||||||
|
加载原生文件。
|
||||||
|
|
||||||
|
```
|
||||||
|
状态流程:
|
||||||
|
LoadBuildinRawBundle
|
||||||
|
├── Android 平台 ──► Error(不支持直接读取)
|
||||||
|
└── 其他平台 ──► RawBundleResult
|
||||||
|
```
|
||||||
|
|
||||||
|
#### DBFSLoadInstantBundleOperation
|
||||||
|
|
||||||
|
加载团结引擎(Tuanjie)专用资源包(需要 `TUANJIE_1_7_OR_NEWER` 宏)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 内部操作类
|
||||||
|
|
||||||
|
### LoadBuildinCatalogFileOperation
|
||||||
|
|
||||||
|
加载 Catalog 目录文件。
|
||||||
|
|
||||||
|
```
|
||||||
|
状态流程:
|
||||||
|
TryLoadFileData
|
||||||
|
├── 文件存在 ──► File.ReadAllBytes
|
||||||
|
└── 文件不存在 ──► RequestFileData (UnityWebRequest)
|
||||||
|
↓
|
||||||
|
LoadCatalog
|
||||||
|
└── CatalogTools.DeserializeFromBinary
|
||||||
|
```
|
||||||
|
|
||||||
|
### CopyBuildinFileOperation
|
||||||
|
|
||||||
|
拷贝内置文件到目标路径。
|
||||||
|
|
||||||
|
```
|
||||||
|
状态流程:
|
||||||
|
CheckFileExist
|
||||||
|
├── 目标已存在 ──► Done (Succeed)
|
||||||
|
└── 目标不存在 ──► TryCopyFile
|
||||||
|
↓
|
||||||
|
TryCopyFile
|
||||||
|
├── 源文件存在 ──► File.Copy
|
||||||
|
└── 源文件不存在 ──► UnpackFile (UnityWebRequest)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 解压机制
|
||||||
|
|
||||||
|
### 自动解压条件
|
||||||
|
|
||||||
|
在 Android/OpenHarmony 平台上,以下情况需要解压到沙盒:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
protected virtual bool IsUnpackBundleFile(PackageBundle bundle)
|
||||||
|
{
|
||||||
|
#if UNITY_ANDROID || UNITY_OPENHARMONY
|
||||||
|
if (bundle.Encrypted) // 加密资源
|
||||||
|
return true;
|
||||||
|
if (bundle.BundleType == RawBundle) // 原生文件
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 解压文件系统
|
||||||
|
|
||||||
|
内置文件系统在创建时会自动创建一个 `DefaultUnpackFileSystem` 实例:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public virtual void OnCreate(string packageName, string packageRoot)
|
||||||
|
{
|
||||||
|
// 创建解压文件系统
|
||||||
|
var remoteServices = new DefaultUnpackRemoteServices(_packageRoot);
|
||||||
|
_unpackFileSystem = new DefaultUnpackFileSystem();
|
||||||
|
_unpackFileSystem.SetParameter(REMOTE_SERVICES, remoteServices);
|
||||||
|
_unpackFileSystem.SetParameter(FILE_VERIFY_LEVEL, FileVerifyLevel);
|
||||||
|
// ... 其他参数
|
||||||
|
_unpackFileSystem.OnCreate(packageName, UnpackFileSystemRoot);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 平台差异处理
|
||||||
|
|
||||||
|
### Android 平台限制
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ Android 平台特殊处理 │
|
||||||
|
├─────────────────────────────────────────────────────────────┤
|
||||||
|
│ StreamingAssets 文件位于 APK 压缩包内,无法直接访问: │
|
||||||
|
│ │
|
||||||
|
│ ✓ AssetBundle.LoadFromFile 支持(Unity 内部处理) │
|
||||||
|
│ ✗ File.ReadAllBytes 不支持 │
|
||||||
|
│ ✗ File.Exists 不支持 │
|
||||||
|
│ ✓ UnityWebRequest 支持(jar:file:// 协议) │
|
||||||
|
│ │
|
||||||
|
│ 解决方案: │
|
||||||
|
│ 1. 加密资源 → 自动解压到沙盒 │
|
||||||
|
│ 2. 原生文件 → 自动解压到沙盒 │
|
||||||
|
│ 3. Catalog → 使用 UnityWebRequest 读取 │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### WebGL 平台
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
#if UNITY_WEBGL
|
||||||
|
_steps = ESteps.Done;
|
||||||
|
Status = EOperationStatus.Failed;
|
||||||
|
Error = $"{nameof(DefaultBuildinFileSystem)} is not support WEBGL platform !";
|
||||||
|
#endif
|
||||||
|
```
|
||||||
|
|
||||||
|
WebGL 平台不支持 DefaultBuildinFileSystem,应使用 `DefaultWebServerFileSystem`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 使用示例
|
||||||
|
|
||||||
|
### 基础配置
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 创建内置文件系统参数
|
||||||
|
var buildinParams = FileSystemParameters.CreateDefaultBuildinFileSystemParameters();
|
||||||
|
|
||||||
|
// 初始化包裹
|
||||||
|
var initParams = new OfflinePlayModeParameters();
|
||||||
|
initParams.BuildinFileSystemParameters = buildinParams;
|
||||||
|
var initOp = package.InitializeAsync(initParams);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 配置解密服务
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var buildinParams = FileSystemParameters.CreateDefaultBuildinFileSystemParameters();
|
||||||
|
|
||||||
|
// 设置解密服务
|
||||||
|
buildinParams.AddParameter(
|
||||||
|
FileSystemParametersDefine.DECRYPTION_SERVICES,
|
||||||
|
new MyDecryptionServices()
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 配置清单拷贝
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var buildinParams = FileSystemParameters.CreateDefaultBuildinFileSystemParameters();
|
||||||
|
|
||||||
|
// 启用清单拷贝(用于离线模式切换到联机模式)
|
||||||
|
buildinParams.AddParameter(
|
||||||
|
FileSystemParametersDefine.COPY_BUILDIN_PACKAGE_MANIFEST,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
// 可选:指定拷贝目标目录
|
||||||
|
buildinParams.AddParameter(
|
||||||
|
FileSystemParametersDefine.COPY_BUILDIN_PACKAGE_MANIFEST_DEST_ROOT,
|
||||||
|
"/custom/path"
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 禁用 Catalog 文件
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var buildinParams = FileSystemParameters.CreateDefaultBuildinFileSystemParameters();
|
||||||
|
|
||||||
|
// 禁用 Catalog(所有资源视为内置)
|
||||||
|
buildinParams.AddParameter(
|
||||||
|
FileSystemParametersDefine.DISABLE_CATALOG_FILE,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 配置解压文件系统根目录
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var buildinParams = FileSystemParameters.CreateDefaultBuildinFileSystemParameters();
|
||||||
|
|
||||||
|
// 设置解压文件系统的根目录
|
||||||
|
buildinParams.AddParameter(
|
||||||
|
FileSystemParametersDefine.UNPACK_FILE_SYSTEM_ROOT,
|
||||||
|
"/custom/unpack/path"
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 参数常量
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 安装清理
|
||||||
|
FileSystemParametersDefine.INSTALL_CLEAR_MODE // EOverwriteInstallClearMode
|
||||||
|
|
||||||
|
// 文件校验
|
||||||
|
FileSystemParametersDefine.FILE_VERIFY_LEVEL // EFileVerifyLevel
|
||||||
|
FileSystemParametersDefine.FILE_VERIFY_MAX_CONCURRENCY // int
|
||||||
|
|
||||||
|
// 文件配置
|
||||||
|
FileSystemParametersDefine.APPEND_FILE_EXTENSION // bool
|
||||||
|
FileSystemParametersDefine.DISABLE_CATALOG_FILE // bool
|
||||||
|
|
||||||
|
// 清单拷贝
|
||||||
|
FileSystemParametersDefine.COPY_BUILDIN_PACKAGE_MANIFEST // bool
|
||||||
|
FileSystemParametersDefine.COPY_BUILDIN_PACKAGE_MANIFEST_DEST_ROOT // string
|
||||||
|
|
||||||
|
// 解压配置
|
||||||
|
FileSystemParametersDefine.UNPACK_FILE_SYSTEM_ROOT // string
|
||||||
|
|
||||||
|
// 服务接口
|
||||||
|
FileSystemParametersDefine.DECRYPTION_SERVICES // IDecryptionServices
|
||||||
|
FileSystemParametersDefine.MANIFEST_SERVICES // IManifestRestoreServices
|
||||||
|
FileSystemParametersDefine.COPY_LOCAL_FILE_SERVICES // ICopyLocalFileServices
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 类继承关系
|
||||||
|
|
||||||
|
```
|
||||||
|
IFileSystem
|
||||||
|
└── DefaultBuildinFileSystem
|
||||||
|
└── (内部持有) DefaultUnpackFileSystem
|
||||||
|
|
||||||
|
FSInitializeFileSystemOperation
|
||||||
|
└── DBFSInitializeOperation
|
||||||
|
|
||||||
|
FSRequestPackageVersionOperation
|
||||||
|
└── DBFSRequestPackageVersionOperation
|
||||||
|
|
||||||
|
FSLoadPackageManifestOperation
|
||||||
|
└── DBFSLoadPackageManifestOperation
|
||||||
|
|
||||||
|
FSLoadBundleOperation
|
||||||
|
├── DBFSLoadAssetBundleOperation
|
||||||
|
├── DBFSLoadRawBundleOperation
|
||||||
|
└── DBFSLoadInstantBundleOperation (Tuanjie)
|
||||||
|
|
||||||
|
AsyncOperationBase
|
||||||
|
├── LoadBuildinCatalogFileOperation
|
||||||
|
├── CopyBuildinFileOperation
|
||||||
|
├── LoadBuildinPackageManifestOperation
|
||||||
|
├── RequestBuildinPackageHashOperation
|
||||||
|
└── RequestBuildinPackageVersionOperation
|
||||||
|
|
||||||
|
BundleResult
|
||||||
|
├── AssetBundleResult ← AssetBundle 资源
|
||||||
|
└── RawBundleResult ← 原生文件
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **WebGL 不支持**:DefaultBuildinFileSystem 不支持 WebGL 平台
|
||||||
|
2. **Android 限制**:Android 平台无法直接读取 StreamingAssets 中的原生文件
|
||||||
|
3. **Catalog 文件**:构建时需要生成 Catalog 文件,否则需要禁用 Catalog 功能
|
||||||
|
4. **解压目录**:解压的文件存储在 `UnpackFileSystemRoot` 指定的目录
|
||||||
|
5. **加密资源**:加密资源在 Android/OpenHarmony 平台会自动解压到沙盒
|
||||||
|
6. **清单拷贝**:启用清单拷贝可以支持从离线模式平滑切换到联机模式
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 7ec531875d1515b4496e0e9035e63661
|
||||||
|
TextScriptImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -23,10 +23,14 @@ namespace YooAsset
|
|||||||
protected string _cacheManifestFilesRoot;
|
protected string _cacheManifestFilesRoot;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 下载中心
|
/// 下载调度器
|
||||||
/// 说明:当异步操作任务终止的时候,所有下载子任务都会一同被终止!
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DownloadCenterOperation DownloadCenter { set; get; }
|
public DownloadSchedulerOperation DownloadScheduler { set; get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 下载后台接口
|
||||||
|
/// </summary>
|
||||||
|
public IDownloadBackend DownloadBackend { private set; get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 包裹名称
|
/// 包裹名称
|
||||||
@@ -56,6 +60,11 @@ namespace YooAsset
|
|||||||
}
|
}
|
||||||
|
|
||||||
#region 自定义参数
|
#region 自定义参数
|
||||||
|
/// <summary>
|
||||||
|
/// 自定义参数:UnityWebRequest 创建委托
|
||||||
|
/// </summary>
|
||||||
|
public UnityWebRequestCreator WebRequestCreator { private set; get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 自定义参数:远程服务接口的实例类
|
/// 自定义参数:远程服务接口的实例类
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -73,8 +82,10 @@ namespace YooAsset
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 自定义参数:初始化的时候缓存文件校验最大并发数
|
/// 自定义参数:初始化的时候缓存文件校验最大并发数
|
||||||
|
/// 默认值:32(推荐范围 1-128)
|
||||||
|
/// 说明:过大的值可能导致线程池任务过多,影响系统稳定性
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int FileVerifyMaxConcurrency { private set; get; } = int.MaxValue;
|
public int FileVerifyMaxConcurrency { private set; get; } = 32;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 自定义参数:数据文件追加文件格式
|
/// 自定义参数:数据文件追加文件格式
|
||||||
@@ -88,18 +99,22 @@ namespace YooAsset
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 自定义参数:最大并发连接数
|
/// 自定义参数:最大并发连接数
|
||||||
|
/// 默认值:10(推荐范围 1-32)
|
||||||
|
/// 说明:过大的并发数可能被服务器限流,也会增加本地资源消耗
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int DownloadMaxConcurrency { private set; get; } = int.MaxValue;
|
public int DownloadMaxConcurrency { private set; get; } = 10;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 自定义参数:每帧发起的最大请求数
|
/// 自定义参数:每帧发起的最大请求数
|
||||||
|
/// 默认值:5(推荐范围 1-10)
|
||||||
|
/// 说明:避免单帧发起过多请求导致卡顿
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int DownloadMaxRequestPerFrame { private set; get; } = int.MaxValue;
|
public int DownloadMaxRequestPerFrame { private set; get; } = 5;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 自定义参数:下载任务的看门狗机制监控时间
|
/// 自定义参数:下载任务的看门狗机制监控时间
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int DownloadWatchDogTime { private set; get; } = int.MaxValue;
|
public int DownloadWatchDogTime { private set; get; } = 0;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 自定义参数:启用断点续传的最小尺寸
|
/// 自定义参数:启用断点续传的最小尺寸
|
||||||
@@ -227,7 +242,15 @@ namespace YooAsset
|
|||||||
|
|
||||||
public virtual void SetParameter(string name, object value)
|
public virtual void SetParameter(string name, object value)
|
||||||
{
|
{
|
||||||
if (name == FileSystemParametersDefine.REMOTE_SERVICES)
|
if (name == FileSystemParametersDefine.DOWNLOAD_BACKEND)
|
||||||
|
{
|
||||||
|
DownloadBackend = (IDownloadBackend)value;
|
||||||
|
}
|
||||||
|
else if (name == FileSystemParametersDefine.UNITY_WEB_REQUEST_CREATOR)
|
||||||
|
{
|
||||||
|
WebRequestCreator = (UnityWebRequestCreator)value;
|
||||||
|
}
|
||||||
|
else if (name == FileSystemParametersDefine.REMOTE_SERVICES)
|
||||||
{
|
{
|
||||||
RemoteServices = (IRemoteServices)value;
|
RemoteServices = (IRemoteServices)value;
|
||||||
}
|
}
|
||||||
@@ -242,7 +265,14 @@ namespace YooAsset
|
|||||||
else if (name == FileSystemParametersDefine.FILE_VERIFY_MAX_CONCURRENCY)
|
else if (name == FileSystemParametersDefine.FILE_VERIFY_MAX_CONCURRENCY)
|
||||||
{
|
{
|
||||||
int convertValue = Convert.ToInt32(value);
|
int convertValue = Convert.ToInt32(value);
|
||||||
FileVerifyMaxConcurrency = Mathf.Clamp(convertValue, 1, int.MaxValue);
|
if (convertValue > 256)
|
||||||
|
{
|
||||||
|
YooLogger.Warning($"FILE_VERIFY_MAX_CONCURRENCY value {convertValue} is too large, clamped to 256. Recommended range: 1 - 128.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 限制在合理范围内:1-256
|
||||||
|
// 超过 256 的并发数对于文件验证来说没有意义,反而会增加线程池压力
|
||||||
|
FileVerifyMaxConcurrency = Mathf.Clamp(convertValue, 1, 256);
|
||||||
}
|
}
|
||||||
else if (name == FileSystemParametersDefine.APPEND_FILE_EXTENSION)
|
else if (name == FileSystemParametersDefine.APPEND_FILE_EXTENSION)
|
||||||
{
|
{
|
||||||
@@ -255,17 +285,27 @@ namespace YooAsset
|
|||||||
else if (name == FileSystemParametersDefine.DOWNLOAD_MAX_CONCURRENCY)
|
else if (name == FileSystemParametersDefine.DOWNLOAD_MAX_CONCURRENCY)
|
||||||
{
|
{
|
||||||
int convertValue = Convert.ToInt32(value);
|
int convertValue = Convert.ToInt32(value);
|
||||||
DownloadMaxConcurrency = Mathf.Clamp(convertValue, 1, int.MaxValue);
|
if (convertValue > 64)
|
||||||
|
{
|
||||||
|
YooLogger.Warning($"DOWNLOAD_MAX_CONCURRENCY value {convertValue} is too large, clamped to 64. Recommended range: 1 - 32.");
|
||||||
|
}
|
||||||
|
|
||||||
|
DownloadMaxConcurrency = Mathf.Clamp(convertValue, 1, 64);
|
||||||
}
|
}
|
||||||
else if (name == FileSystemParametersDefine.DOWNLOAD_MAX_REQUEST_PER_FRAME)
|
else if (name == FileSystemParametersDefine.DOWNLOAD_MAX_REQUEST_PER_FRAME)
|
||||||
{
|
{
|
||||||
int convertValue = Convert.ToInt32(value);
|
int convertValue = Convert.ToInt32(value);
|
||||||
DownloadMaxRequestPerFrame = Mathf.Clamp(convertValue, 1, int.MaxValue);
|
if (convertValue > 20)
|
||||||
|
{
|
||||||
|
YooLogger.Warning($"DOWNLOAD_MAX_REQUEST_PER_FRAME value {convertValue} is too large, clamped to 20. Recommended range: 1 - 10.");
|
||||||
|
}
|
||||||
|
|
||||||
|
DownloadMaxRequestPerFrame = Mathf.Clamp(convertValue, 1, 20);
|
||||||
}
|
}
|
||||||
else if (name == FileSystemParametersDefine.DOWNLOAD_WATCH_DOG_TIME)
|
else if (name == FileSystemParametersDefine.DOWNLOAD_WATCH_DOG_TIME)
|
||||||
{
|
{
|
||||||
int convertValue = Convert.ToInt32(value);
|
int convertValue = Convert.ToInt32(value);
|
||||||
DownloadWatchDogTime = Mathf.Clamp(convertValue, 1, int.MaxValue);
|
DownloadWatchDogTime = Mathf.Clamp(convertValue, 0, int.MaxValue);
|
||||||
}
|
}
|
||||||
else if (name == FileSystemParametersDefine.RESUME_DOWNLOAD_MINMUM_SIZE)
|
else if (name == FileSystemParametersDefine.RESUME_DOWNLOAD_MINMUM_SIZE)
|
||||||
{
|
{
|
||||||
@@ -304,13 +344,23 @@ namespace YooAsset
|
|||||||
_cacheBundleFilesRoot = PathUtility.Combine(_packageRoot, DefaultCacheFileSystemDefine.BundleFilesFolderName);
|
_cacheBundleFilesRoot = PathUtility.Combine(_packageRoot, DefaultCacheFileSystemDefine.BundleFilesFolderName);
|
||||||
_cacheManifestFilesRoot = PathUtility.Combine(_packageRoot, DefaultCacheFileSystemDefine.ManifestFilesFolderName);
|
_cacheManifestFilesRoot = PathUtility.Combine(_packageRoot, DefaultCacheFileSystemDefine.ManifestFilesFolderName);
|
||||||
_tempFilesRoot = PathUtility.Combine(_packageRoot, DefaultCacheFileSystemDefine.TempFilesFolderName);
|
_tempFilesRoot = PathUtility.Combine(_packageRoot, DefaultCacheFileSystemDefine.TempFilesFolderName);
|
||||||
|
|
||||||
|
// 创建默认的下载后台接口
|
||||||
|
if (DownloadBackend == null)
|
||||||
|
DownloadBackend = new UnityWebRequestBackend(WebRequestCreator);
|
||||||
}
|
}
|
||||||
public virtual void OnDestroy()
|
public virtual void OnDestroy()
|
||||||
{
|
{
|
||||||
if (DownloadCenter != null)
|
if (DownloadScheduler != null)
|
||||||
{
|
{
|
||||||
DownloadCenter.AbortOperation();
|
DownloadScheduler.Dispose();
|
||||||
DownloadCenter = null;
|
DownloadScheduler = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DownloadBackend != null)
|
||||||
|
{
|
||||||
|
DownloadBackend.Dispose();
|
||||||
|
DownloadBackend = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -476,7 +526,7 @@ namespace YooAsset
|
|||||||
{
|
{
|
||||||
if (_records.ContainsKey(bundle.BundleGUID))
|
if (_records.ContainsKey(bundle.BundleGUID))
|
||||||
{
|
{
|
||||||
throw new Exception("Should never get here !");
|
throw new YooInternalException();
|
||||||
}
|
}
|
||||||
|
|
||||||
string infoFilePath = GetBundleInfoFilePath(bundle);
|
string infoFilePath = GetBundleInfoFilePath(bundle);
|
||||||
@@ -498,9 +548,9 @@ namespace YooAsset
|
|||||||
// 写入文件信息
|
// 写入文件信息
|
||||||
WriteBundleInfoFile(infoFilePath, bundle.FileCRC, bundle.FileSize);
|
WriteBundleInfoFile(infoFilePath, bundle.FileCRC, bundle.FileSize);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
YooLogger.Error($"Failed to write cache file ! {e.Message}");
|
YooLogger.Error($"Failed to write cache file ! {ex.Message}");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,9 +37,9 @@ namespace YooAsset
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
YooLogger.Error($"Failed to delete cache file ! {e.Message}");
|
YooLogger.Error($"Failed to delete cache file ! {ex.Message}");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,9 +33,9 @@ namespace YooAsset
|
|||||||
{
|
{
|
||||||
Directory.Delete(FileRootPath, true);
|
Directory.Delete(FileRootPath, true);
|
||||||
}
|
}
|
||||||
catch (System.Exception e)
|
catch (System.Exception ex)
|
||||||
{
|
{
|
||||||
YooLogger.Warning($"Failed to delete cache bundle folder : {e}");
|
YooLogger.Warning($"Failed to delete cache bundle folder : {ex}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ namespace YooAsset
|
|||||||
CheckAppFootPrint,
|
CheckAppFootPrint,
|
||||||
SearchCacheFiles,
|
SearchCacheFiles,
|
||||||
VerifyCacheFiles,
|
VerifyCacheFiles,
|
||||||
CreateDownloadCenter,
|
CreateDownloadScheduler,
|
||||||
Done,
|
Done,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +110,7 @@ namespace YooAsset
|
|||||||
|
|
||||||
if (_verifyCacheFilesOp.Status == EOperationStatus.Succeed)
|
if (_verifyCacheFilesOp.Status == EOperationStatus.Succeed)
|
||||||
{
|
{
|
||||||
_steps = ESteps.CreateDownloadCenter;
|
_steps = ESteps.CreateDownloadScheduler;
|
||||||
YooLogger.Log($"Package '{_fileSystem.PackageName}' '{_fileSystem.GetType().Name}' cached files count : {_fileSystem.FileCount}");
|
YooLogger.Log($"Package '{_fileSystem.PackageName}' '{_fileSystem.GetType().Name}' cached files count : {_fileSystem.FileCount}");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -121,13 +121,13 @@ namespace YooAsset
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_steps == ESteps.CreateDownloadCenter)
|
if (_steps == ESteps.CreateDownloadScheduler)
|
||||||
{
|
{
|
||||||
// 注意:下载中心作为独立任务运行!
|
// 注意:下载中心作为独立任务运行!
|
||||||
if (_fileSystem.DownloadCenter == null)
|
if (_fileSystem.DownloadScheduler == null)
|
||||||
{
|
{
|
||||||
_fileSystem.DownloadCenter = new DownloadCenterOperation(_fileSystem);
|
_fileSystem.DownloadScheduler = new DownloadSchedulerOperation(_fileSystem);
|
||||||
OperationSystem.StartOperation(_fileSystem.PackageName, _fileSystem.DownloadCenter);
|
OperationSystem.StartOperation(_fileSystem.PackageName, _fileSystem.DownloadScheduler);
|
||||||
}
|
}
|
||||||
|
|
||||||
_steps = ESteps.Done;
|
_steps = ESteps.Done;
|
||||||
|
|||||||
@@ -320,11 +320,11 @@ namespace YooAsset
|
|||||||
File.Move(recordFileElement.DataFilePath, filePath);
|
File.Move(recordFileElement.DataFilePath, filePath);
|
||||||
_steps = ESteps.LoadCacheRawBundle;
|
_steps = ESteps.LoadCacheRawBundle;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_steps = ESteps.Done;
|
_steps = ESteps.Done;
|
||||||
Status = EOperationStatus.Failed;
|
Status = EOperationStatus.Failed;
|
||||||
Error = $"Faild rename raw data file : {e.Message}";
|
Error = $"Faild rename raw data file : {ex.Message}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -1,133 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace YooAsset
|
|
||||||
{
|
|
||||||
internal class DownloadCenterOperation : AsyncOperationBase
|
|
||||||
{
|
|
||||||
private readonly DefaultCacheFileSystem _fileSystem;
|
|
||||||
protected readonly Dictionary<string, UnityDownloadFileOperation> _downloaders = new Dictionary<string, UnityDownloadFileOperation>(1000);
|
|
||||||
protected readonly List<string> _removeList = new List<string>(1000);
|
|
||||||
|
|
||||||
public DownloadCenterOperation(DefaultCacheFileSystem fileSystem)
|
|
||||||
{
|
|
||||||
_fileSystem = fileSystem;
|
|
||||||
}
|
|
||||||
internal override void InternalStart()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
internal override void InternalUpdate()
|
|
||||||
{
|
|
||||||
// 获取可移除的下载器集合
|
|
||||||
_removeList.Clear();
|
|
||||||
foreach (var valuePair in _downloaders)
|
|
||||||
{
|
|
||||||
var downloader = valuePair.Value;
|
|
||||||
downloader.UpdateOperation();
|
|
||||||
if (downloader.IsDone)
|
|
||||||
{
|
|
||||||
_removeList.Add(valuePair.Key);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 注意:主动终止引用计数为零的下载任务
|
|
||||||
if (downloader.RefCount <= 0)
|
|
||||||
{
|
|
||||||
_removeList.Add(valuePair.Key);
|
|
||||||
downloader.AbortOperation();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 移除下载器
|
|
||||||
foreach (var key in _removeList)
|
|
||||||
{
|
|
||||||
if (_downloaders.TryGetValue(key, out var downloader))
|
|
||||||
{
|
|
||||||
Childs.Remove(downloader);
|
|
||||||
_downloaders.Remove(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 最大并发数检测
|
|
||||||
int processCount = GetProcessingOperationCount();
|
|
||||||
if (processCount != _downloaders.Count)
|
|
||||||
{
|
|
||||||
if (processCount < _fileSystem.DownloadMaxConcurrency)
|
|
||||||
{
|
|
||||||
int startCount = _fileSystem.DownloadMaxConcurrency - processCount;
|
|
||||||
if (startCount > _fileSystem.DownloadMaxRequestPerFrame)
|
|
||||||
startCount = _fileSystem.DownloadMaxRequestPerFrame;
|
|
||||||
|
|
||||||
foreach (var operationPair in _downloaders)
|
|
||||||
{
|
|
||||||
var operation = operationPair.Value;
|
|
||||||
if (operation.Status == EOperationStatus.None)
|
|
||||||
{
|
|
||||||
operation.StartOperation();
|
|
||||||
startCount--;
|
|
||||||
if (startCount <= 0)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 创建下载任务
|
|
||||||
/// </summary>
|
|
||||||
public UnityDownloadFileOperation DownloadFileAsync(PackageBundle bundle, string url)
|
|
||||||
{
|
|
||||||
// 查询旧的下载器
|
|
||||||
if (_downloaders.TryGetValue(bundle.BundleGUID, out var oldDownloader))
|
|
||||||
{
|
|
||||||
oldDownloader.Reference();
|
|
||||||
return oldDownloader;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建新的下载器
|
|
||||||
UnityDownloadFileOperation newDownloader;
|
|
||||||
bool isRequestLocalFile = DownloadSystemHelper.IsRequestLocalFile(url);
|
|
||||||
if (isRequestLocalFile)
|
|
||||||
{
|
|
||||||
newDownloader = new UnityDownloadLocalFileOperation(_fileSystem, bundle, url);
|
|
||||||
AddChildOperation(newDownloader);
|
|
||||||
_downloaders.Add(bundle.BundleGUID, newDownloader);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (bundle.FileSize >= _fileSystem.ResumeDownloadMinimumSize)
|
|
||||||
{
|
|
||||||
newDownloader = new UnityDownloadResumeFileOperation(_fileSystem, bundle, url);
|
|
||||||
AddChildOperation(newDownloader);
|
|
||||||
_downloaders.Add(bundle.BundleGUID, newDownloader);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
newDownloader = new UnityDownloadNormalFileOperation(_fileSystem, bundle, url);
|
|
||||||
AddChildOperation(newDownloader);
|
|
||||||
_downloaders.Add(bundle.BundleGUID, newDownloader);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
newDownloader.Reference();
|
|
||||||
return newDownloader;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取正在进行中的下载器总数
|
|
||||||
/// </summary>
|
|
||||||
private int GetProcessingOperationCount()
|
|
||||||
{
|
|
||||||
int count = 0;
|
|
||||||
foreach (var operationPair in _downloaders)
|
|
||||||
{
|
|
||||||
var operation = operationPair.Value;
|
|
||||||
if (operation.Status != EOperationStatus.None)
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 768ef2df3433df245a26fec28d022e45
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using UnityEngine;
|
using System.IO;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
namespace YooAsset
|
namespace YooAsset
|
||||||
{
|
{
|
||||||
@@ -17,7 +18,7 @@ namespace YooAsset
|
|||||||
// 下载参数
|
// 下载参数
|
||||||
protected readonly DefaultCacheFileSystem _fileSystem;
|
protected readonly DefaultCacheFileSystem _fileSystem;
|
||||||
protected readonly DownloadFileOptions _options;
|
protected readonly DownloadFileOptions _options;
|
||||||
private UnityDownloadFileOperation _unityDownloadFileOp;
|
private DownloadAndCacheFileOperation _downloadFileOp;
|
||||||
|
|
||||||
protected int _requestCount = 0;
|
protected int _requestCount = 0;
|
||||||
protected float _tryAgainTimer = 0;
|
protected float _tryAgainTimer = 0;
|
||||||
@@ -67,7 +68,7 @@ namespace YooAsset
|
|||||||
}
|
}
|
||||||
|
|
||||||
string url = GetRequestURL();
|
string url = GetRequestURL();
|
||||||
_unityDownloadFileOp = _fileSystem.DownloadCenter.DownloadFileAsync(Bundle, url);
|
_downloadFileOp = _fileSystem.DownloadScheduler.DownloadAndCacheFileAsync(Bundle, url);
|
||||||
_steps = ESteps.CheckRequest;
|
_steps = ESteps.CheckRequest;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,20 +76,16 @@ namespace YooAsset
|
|||||||
if (_steps == ESteps.CheckRequest)
|
if (_steps == ESteps.CheckRequest)
|
||||||
{
|
{
|
||||||
if (IsWaitForAsyncComplete)
|
if (IsWaitForAsyncComplete)
|
||||||
_unityDownloadFileOp.WaitForAsyncComplete();
|
_downloadFileOp.WaitForAsyncComplete();
|
||||||
|
|
||||||
// 因为并发数量限制,下载器可能被挂起!
|
_downloadFileOp.UpdateOperation();
|
||||||
if (_unityDownloadFileOp.Status == EOperationStatus.None)
|
Progress = _downloadFileOp.Progress;
|
||||||
|
DownloadedBytes = _downloadFileOp.DownloadedBytes;
|
||||||
|
DownloadProgress = _downloadFileOp.DownloadProgress;
|
||||||
|
if (_downloadFileOp.IsDone == false)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_unityDownloadFileOp.UpdateOperation();
|
if (_downloadFileOp.Status == EOperationStatus.Succeed)
|
||||||
Progress = _unityDownloadFileOp.Progress;
|
|
||||||
DownloadedBytes = _unityDownloadFileOp.DownloadedBytes;
|
|
||||||
DownloadProgress = _unityDownloadFileOp.DownloadProgress;
|
|
||||||
if (_unityDownloadFileOp.IsDone == false)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (_unityDownloadFileOp.Status == EOperationStatus.Succeed)
|
|
||||||
{
|
{
|
||||||
_steps = ESteps.Done;
|
_steps = ESteps.Done;
|
||||||
Status = EOperationStatus.Succeed;
|
Status = EOperationStatus.Succeed;
|
||||||
@@ -98,13 +95,13 @@ namespace YooAsset
|
|||||||
if (IsWaitForAsyncComplete == false && _failedTryAgain > 0)
|
if (IsWaitForAsyncComplete == false && _failedTryAgain > 0)
|
||||||
{
|
{
|
||||||
_steps = ESteps.TryAgain;
|
_steps = ESteps.TryAgain;
|
||||||
YooLogger.Warning($"Failed download : {_unityDownloadFileOp.URL} Try again !");
|
YooLogger.Warning($"Failed download : {_downloadFileOp.URL} Try again !");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_steps = ESteps.Done;
|
_steps = ESteps.Done;
|
||||||
Status = EOperationStatus.Failed;
|
Status = EOperationStatus.Failed;
|
||||||
Error = _unityDownloadFileOp.Error;
|
Error = _downloadFileOp.Error;
|
||||||
YooLogger.Error(Error);
|
YooLogger.Error(Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -141,9 +138,9 @@ namespace YooAsset
|
|||||||
// 注意:取消下载任务的时候引用计数减一
|
// 注意:取消下载任务的时候引用计数减一
|
||||||
if (_steps != ESteps.Done)
|
if (_steps != ESteps.Done)
|
||||||
{
|
{
|
||||||
if (_unityDownloadFileOp != null)
|
if (_downloadFileOp != null)
|
||||||
{
|
{
|
||||||
_unityDownloadFileOp.Release();
|
_downloadFileOp.Release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace YooAsset
|
|||||||
private readonly DefaultCacheFileSystem _fileSystem;
|
private readonly DefaultCacheFileSystem _fileSystem;
|
||||||
private readonly string _packageVersion;
|
private readonly string _packageVersion;
|
||||||
private readonly int _timeout;
|
private readonly int _timeout;
|
||||||
private UnityWebFileRequestOperation _webFileRequestOp;
|
private IDownloadFileRequest _webFileRequestOp;
|
||||||
private int _requestCount = 0;
|
private int _requestCount = 0;
|
||||||
private ESteps _steps = ESteps.None;
|
private ESteps _steps = ESteps.None;
|
||||||
|
|
||||||
@@ -57,16 +57,16 @@ namespace YooAsset
|
|||||||
string savePath = _fileSystem.GetCachePackageHashFilePath(_packageVersion);
|
string savePath = _fileSystem.GetCachePackageHashFilePath(_packageVersion);
|
||||||
string fileName = YooAssetSettingsData.GetPackageHashFileName(_fileSystem.PackageName, _packageVersion);
|
string fileName = YooAssetSettingsData.GetPackageHashFileName(_fileSystem.PackageName, _packageVersion);
|
||||||
string webURL = GetWebRequestURL(fileName);
|
string webURL = GetWebRequestURL(fileName);
|
||||||
_webFileRequestOp = new UnityWebFileRequestOperation(webURL, savePath, _timeout);
|
int watchdogTime = _fileSystem.DownloadWatchDogTime;
|
||||||
_webFileRequestOp.StartOperation();
|
var args = new DownloadFileRequestArgs(webURL, savePath, _timeout, watchdogTime);
|
||||||
AddChildOperation(_webFileRequestOp);
|
_webFileRequestOp = _fileSystem.DownloadBackend.CreateFileRequest(args);
|
||||||
|
_webFileRequestOp.SendRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
_webFileRequestOp.UpdateOperation();
|
|
||||||
if (_webFileRequestOp.IsDone == false)
|
if (_webFileRequestOp.IsDone == false)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (_webFileRequestOp.Status == EOperationStatus.Succeed)
|
if (_webFileRequestOp.Status == EDownloadRequestStatus.Succeed)
|
||||||
{
|
{
|
||||||
_steps = ESteps.Done;
|
_steps = ESteps.Done;
|
||||||
Status = EOperationStatus.Succeed;
|
Status = EOperationStatus.Succeed;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace YooAsset
|
|||||||
private readonly DefaultCacheFileSystem _fileSystem;
|
private readonly DefaultCacheFileSystem _fileSystem;
|
||||||
private readonly string _packageVersion;
|
private readonly string _packageVersion;
|
||||||
private readonly int _timeout;
|
private readonly int _timeout;
|
||||||
private UnityWebFileRequestOperation _webFileRequestOp;
|
private IDownloadFileRequest _webFileRequestOp;
|
||||||
private int _requestCount = 0;
|
private int _requestCount = 0;
|
||||||
private ESteps _steps = ESteps.None;
|
private ESteps _steps = ESteps.None;
|
||||||
|
|
||||||
@@ -57,16 +57,16 @@ namespace YooAsset
|
|||||||
string savePath = _fileSystem.GetCachePackageManifestFilePath(_packageVersion);
|
string savePath = _fileSystem.GetCachePackageManifestFilePath(_packageVersion);
|
||||||
string fileName = YooAssetSettingsData.GetManifestBinaryFileName(_fileSystem.PackageName, _packageVersion);
|
string fileName = YooAssetSettingsData.GetManifestBinaryFileName(_fileSystem.PackageName, _packageVersion);
|
||||||
string webURL = GetDownloadRequestURL(fileName);
|
string webURL = GetDownloadRequestURL(fileName);
|
||||||
_webFileRequestOp = new UnityWebFileRequestOperation(webURL, savePath, _timeout);
|
int watchdogTime = _fileSystem.DownloadWatchDogTime;
|
||||||
_webFileRequestOp.StartOperation();
|
var args = new DownloadFileRequestArgs(webURL, savePath, _timeout, watchdogTime);
|
||||||
AddChildOperation(_webFileRequestOp);
|
_webFileRequestOp = _fileSystem.DownloadBackend.CreateFileRequest(args);
|
||||||
|
_webFileRequestOp.SendRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
_webFileRequestOp.UpdateOperation();
|
|
||||||
if (_webFileRequestOp.IsDone == false)
|
if (_webFileRequestOp.IsDone == false)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (_webFileRequestOp.Status == EOperationStatus.Succeed)
|
if (_webFileRequestOp.Status == EDownloadRequestStatus.Succeed)
|
||||||
{
|
{
|
||||||
_steps = ESteps.Done;
|
_steps = ESteps.Done;
|
||||||
Status = EOperationStatus.Succeed;
|
Status = EOperationStatus.Succeed;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ namespace YooAsset
|
|||||||
private readonly DefaultCacheFileSystem _fileSystem;
|
private readonly DefaultCacheFileSystem _fileSystem;
|
||||||
private readonly bool _appendTimeTicks;
|
private readonly bool _appendTimeTicks;
|
||||||
private readonly int _timeout;
|
private readonly int _timeout;
|
||||||
private UnityWebTextRequestOperation _webTextRequestOp;
|
private IDownloadTextRequest _webTextRequestOp;
|
||||||
private int _requestCount = 0;
|
private int _requestCount = 0;
|
||||||
private ESteps _steps = ESteps.None;
|
private ESteps _steps = ESteps.None;
|
||||||
|
|
||||||
@@ -45,17 +45,17 @@ namespace YooAsset
|
|||||||
{
|
{
|
||||||
string fileName = YooAssetSettingsData.GetPackageVersionFileName(_fileSystem.PackageName);
|
string fileName = YooAssetSettingsData.GetPackageVersionFileName(_fileSystem.PackageName);
|
||||||
string url = GetWebRequestURL(fileName);
|
string url = GetWebRequestURL(fileName);
|
||||||
_webTextRequestOp = new UnityWebTextRequestOperation(url, _timeout);
|
int watchDogTime = _fileSystem.DownloadWatchDogTime;
|
||||||
_webTextRequestOp.StartOperation();
|
var args = new DownloadDataRequestArgs(url, _timeout, watchDogTime);
|
||||||
AddChildOperation(_webTextRequestOp);
|
_webTextRequestOp = _fileSystem.DownloadBackend.CreateTextRequest(args);
|
||||||
|
_webTextRequestOp.SendRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
_webTextRequestOp.UpdateOperation();
|
Progress = _webTextRequestOp.DownloadProgress;
|
||||||
Progress = _webTextRequestOp.Progress;
|
|
||||||
if (_webTextRequestOp.IsDone == false)
|
if (_webTextRequestOp.IsDone == false)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (_webTextRequestOp.Status == EOperationStatus.Succeed)
|
if (_webTextRequestOp.Status == EDownloadRequestStatus.Succeed)
|
||||||
{
|
{
|
||||||
PackageVersion = _webTextRequestOp.Result;
|
PackageVersion = _webTextRequestOp.Result;
|
||||||
if (string.IsNullOrEmpty(PackageVersion))
|
if (string.IsNullOrEmpty(PackageVersion))
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: bf9991076b60f0f459846f54b0ca6698
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
|
||||||
|
namespace YooAsset
|
||||||
|
{
|
||||||
|
internal abstract class DownloadAndCacheFileOperation : AsyncOperationBase
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 引用计数
|
||||||
|
/// </summary>
|
||||||
|
public int RefCount { private set; get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 下载地址
|
||||||
|
/// </summary>
|
||||||
|
public readonly string URL;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 下载进度
|
||||||
|
/// </summary>
|
||||||
|
public float DownloadProgress { get; protected set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 下载字节
|
||||||
|
/// </summary>
|
||||||
|
public long DownloadedBytes { get; protected set; }
|
||||||
|
|
||||||
|
public DownloadAndCacheFileOperation(string url)
|
||||||
|
{
|
||||||
|
URL = url;
|
||||||
|
}
|
||||||
|
internal override string InternalGetDesc()
|
||||||
|
{
|
||||||
|
return $"RefCount : {RefCount}";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 减少引用计数
|
||||||
|
/// </summary>
|
||||||
|
public void Release()
|
||||||
|
{
|
||||||
|
RefCount--;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 增加引用计数
|
||||||
|
/// </summary>
|
||||||
|
public void Reference()
|
||||||
|
{
|
||||||
|
RefCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 2954a64fd419d5a4b9d0a102260d193c
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -1,94 +1,79 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using UnityEngine;
|
|
||||||
using UnityEngine.Networking;
|
|
||||||
|
|
||||||
namespace YooAsset
|
namespace YooAsset
|
||||||
{
|
{
|
||||||
internal sealed class UnityDownloadLocalFileOperation : UnityDownloadFileOperation
|
internal sealed class DownloadAndCacheLocalFileOperation : DownloadAndCacheFileOperation
|
||||||
{
|
{
|
||||||
|
private enum ESteps
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
CheckCopy,
|
||||||
|
CopyLocalFile,
|
||||||
|
CreateRequest,
|
||||||
|
CheckRequest,
|
||||||
|
VerifyBundleFile,
|
||||||
|
CacheBundleFile,
|
||||||
|
Done,
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly DefaultCacheFileSystem _fileSystem;
|
||||||
|
private readonly PackageBundle _bundle;
|
||||||
|
private readonly string _tempFilePath;
|
||||||
|
private IDownloadRequest _request;
|
||||||
private VerifyTempFileOperation _verifyOperation;
|
private VerifyTempFileOperation _verifyOperation;
|
||||||
private ESteps _steps = ESteps.None;
|
private ESteps _steps = ESteps.None;
|
||||||
|
|
||||||
internal UnityDownloadLocalFileOperation(DefaultCacheFileSystem fileSystem, PackageBundle bundle, string url)
|
internal DownloadAndCacheLocalFileOperation(DefaultCacheFileSystem fileSystem, PackageBundle bundle, string url) : base(url)
|
||||||
: base(fileSystem, bundle, url)
|
|
||||||
{
|
{
|
||||||
|
_fileSystem = fileSystem;
|
||||||
|
_bundle = bundle;
|
||||||
|
_tempFilePath = _fileSystem.GetTempFilePath(_bundle);
|
||||||
}
|
}
|
||||||
internal override void InternalStart()
|
internal override void InternalStart()
|
||||||
{
|
{
|
||||||
if (_fileSystem.CopyLocalFileServices != null)
|
_steps = ESteps.CheckCopy;
|
||||||
_steps = ESteps.CopyLocalFile;
|
|
||||||
else
|
|
||||||
_steps = ESteps.CreateRequest;
|
|
||||||
}
|
}
|
||||||
internal override void InternalUpdate()
|
internal override void InternalUpdate()
|
||||||
{
|
{
|
||||||
if (_steps == ESteps.None || _steps == ESteps.Done)
|
if (_steps == ESteps.None || _steps == ESteps.Done)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// 创建下载器
|
// 检测文件拷贝
|
||||||
if (_steps == ESteps.CreateRequest)
|
if (_steps == ESteps.CheckCopy)
|
||||||
{
|
{
|
||||||
|
// 删除历史缓存文件
|
||||||
FileUtility.CreateFileDirectory(_tempFilePath);
|
FileUtility.CreateFileDirectory(_tempFilePath);
|
||||||
if (File.Exists(_tempFilePath))
|
if (File.Exists(_tempFilePath))
|
||||||
File.Delete(_tempFilePath);
|
File.Delete(_tempFilePath);
|
||||||
|
|
||||||
CreateWebRequest();
|
if (_fileSystem.CopyLocalFileServices != null)
|
||||||
_steps = ESteps.Download;
|
_steps = ESteps.CopyLocalFile;
|
||||||
}
|
|
||||||
|
|
||||||
// 检测下载结果
|
|
||||||
if (_steps == ESteps.Download)
|
|
||||||
{
|
|
||||||
DownloadProgress = _webRequest.downloadProgress;
|
|
||||||
DownloadedBytes = (long)_webRequest.downloadedBytes;
|
|
||||||
Progress = DownloadProgress;
|
|
||||||
|
|
||||||
UpdateWatchDog();
|
|
||||||
if (_webRequest.isDone == false)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// 检查网络错误
|
|
||||||
if (CheckRequestResult())
|
|
||||||
{
|
|
||||||
_steps = ESteps.VerifyFile;
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
_steps = ESteps.CreateRequest;
|
||||||
_steps = ESteps.Done;
|
|
||||||
Status = EOperationStatus.Failed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 注意:最终释放请求器
|
// 拷贝本地文件
|
||||||
DisposeRequest();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 拷贝内置文件
|
|
||||||
if (_steps == ESteps.CopyLocalFile)
|
if (_steps == ESteps.CopyLocalFile)
|
||||||
{
|
{
|
||||||
FileUtility.CreateFileDirectory(_tempFilePath);
|
|
||||||
if (File.Exists(_tempFilePath))
|
|
||||||
File.Delete(_tempFilePath);
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
//TODO 团结引擎,在某些机型(红米),拷贝包内文件会小概率失败!需要借助其它方式来拷贝包内文件。
|
//TODO 团结引擎,在某些机型(红米),拷贝包内文件会小概率失败!需要借助其它方式来拷贝包内文件。
|
||||||
var localFileInfo = new LocalFileInfo();
|
var localFileInfo = new LocalFileInfo();
|
||||||
localFileInfo.PackageName = _fileSystem.PackageName;
|
localFileInfo.PackageName = _fileSystem.PackageName;
|
||||||
localFileInfo.BundleName = _bundle.BundleName;
|
localFileInfo.BundleName = _bundle.BundleName;
|
||||||
localFileInfo.SourceFileURL = _requestURL;
|
localFileInfo.SourceFileURL = URL;
|
||||||
_fileSystem.CopyLocalFileServices.CopyFile(localFileInfo, _tempFilePath);
|
_fileSystem.CopyLocalFileServices.CopyFile(localFileInfo, _tempFilePath);
|
||||||
if (File.Exists(_tempFilePath))
|
if (File.Exists(_tempFilePath))
|
||||||
{
|
{
|
||||||
DownloadProgress = 1f;
|
DownloadProgress = 1f;
|
||||||
DownloadedBytes = _bundle.FileSize;
|
DownloadedBytes = _bundle.FileSize;
|
||||||
Progress = DownloadProgress;
|
_steps = ESteps.VerifyBundleFile;
|
||||||
_steps = ESteps.VerifyFile;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_steps = ESteps.Done;
|
_steps = ESteps.Done;
|
||||||
Status = EOperationStatus.Failed;
|
Status = EOperationStatus.Failed;
|
||||||
Error = $"Failed copy local file : {_requestURL}";
|
Error = $"Failed copy local file : {URL}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (System.Exception ex)
|
catch (System.Exception ex)
|
||||||
@@ -99,8 +84,44 @@ namespace YooAsset
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证下载文件
|
// 创建下载请求
|
||||||
if (_steps == ESteps.VerifyFile)
|
if (_steps == ESteps.CreateRequest)
|
||||||
|
{
|
||||||
|
int watchdogTime = _fileSystem.DownloadWatchDogTime;
|
||||||
|
int timeout = 0; //注意:文件下载不做超时检测
|
||||||
|
var args = new DownloadFileRequestArgs(URL, _tempFilePath, timeout, watchdogTime);
|
||||||
|
_request = _fileSystem.DownloadBackend.CreateFileRequest(args);
|
||||||
|
_request.SendRequest();
|
||||||
|
_steps = ESteps.CheckRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检测下载结果
|
||||||
|
if (_steps == ESteps.CheckRequest)
|
||||||
|
{
|
||||||
|
DownloadProgress = _request.DownloadProgress;
|
||||||
|
DownloadedBytes = _request.DownloadedBytes;
|
||||||
|
Progress = DownloadProgress;
|
||||||
|
if (_request.IsDone == false)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 检查网络错误
|
||||||
|
if (_request.Status == EDownloadRequestStatus.Succeed)
|
||||||
|
{
|
||||||
|
_steps = ESteps.VerifyBundleFile;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_steps = ESteps.Done;
|
||||||
|
Status = EOperationStatus.Failed;
|
||||||
|
Error = _request.Error;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 最终释放请求器
|
||||||
|
_request.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证下载结果
|
||||||
|
if (_steps == ESteps.VerifyBundleFile)
|
||||||
{
|
{
|
||||||
if (_verifyOperation == null)
|
if (_verifyOperation == null)
|
||||||
{
|
{
|
||||||
@@ -118,6 +139,23 @@ namespace YooAsset
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
if (_verifyOperation.Status == EOperationStatus.Succeed)
|
if (_verifyOperation.Status == EOperationStatus.Succeed)
|
||||||
|
{
|
||||||
|
_steps = ESteps.CacheBundleFile;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_steps = ESteps.Done;
|
||||||
|
Status = EOperationStatus.Failed;
|
||||||
|
Error = _verifyOperation.Error;
|
||||||
|
|
||||||
|
// 注意:验证失败后直接删除文件
|
||||||
|
if (File.Exists(_tempFilePath))
|
||||||
|
File.Delete(_tempFilePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 缓存文件
|
||||||
|
if (_steps == ESteps.CacheBundleFile)
|
||||||
{
|
{
|
||||||
if (_fileSystem.WriteCacheBundleFile(_bundle, _tempFilePath))
|
if (_fileSystem.WriteCacheBundleFile(_bundle, _tempFilePath))
|
||||||
{
|
{
|
||||||
@@ -130,41 +168,32 @@ namespace YooAsset
|
|||||||
Status = EOperationStatus.Failed;
|
Status = EOperationStatus.Failed;
|
||||||
Error = $"{_fileSystem.GetType().FullName} failed to write file !";
|
Error = $"{_fileSystem.GetType().FullName} failed to write file !";
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_steps = ESteps.Done;
|
|
||||||
Status = EOperationStatus.Failed;
|
|
||||||
Error = _verifyOperation.Error;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 注意:验证完成后直接删除文件
|
// 注意:缓存完成后直接删除临时文件
|
||||||
if (File.Exists(_tempFilePath))
|
if (File.Exists(_tempFilePath))
|
||||||
File.Delete(_tempFilePath);
|
File.Delete(_tempFilePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
internal override void InternalAbort()
|
||||||
|
{
|
||||||
|
if (_request != null)
|
||||||
|
_request.AbortRequest();
|
||||||
|
}
|
||||||
internal override void InternalWaitForAsyncComplete()
|
internal override void InternalWaitForAsyncComplete()
|
||||||
{
|
{
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
|
//TODO 更新下载后台,防止无限挂起
|
||||||
|
_fileSystem.DownloadBackend.Update();
|
||||||
|
|
||||||
//TODO 等待导入或解压本地文件完毕,该操作会挂起主线程!
|
//TODO 等待导入或解压本地文件完毕,该操作会挂起主线程!
|
||||||
InternalUpdate();
|
InternalUpdate();
|
||||||
if (IsDone)
|
if (IsDone)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// 短暂休眠避免完全卡死
|
//TODO 短暂休眠避免完全卡死
|
||||||
System.Threading.Thread.Sleep(1);
|
System.Threading.Thread.Sleep(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateWebRequest()
|
|
||||||
{
|
|
||||||
DownloadHandlerFile handler = new DownloadHandlerFile(_tempFilePath);
|
|
||||||
handler.removeFileOnAbort = true;
|
|
||||||
_webRequest = DownloadSystemHelper.NewUnityWebRequestGet(_requestURL);
|
|
||||||
_webRequest.downloadHandler = handler;
|
|
||||||
_webRequest.disposeDownloadHandlerOnDispose = true;
|
|
||||||
_webRequest.SendWebRequest();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 47700ac672942834f8d5a7c59783d88e
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,206 @@
|
|||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace YooAsset
|
||||||
|
{
|
||||||
|
internal sealed class DownloadAndCacheRemoteFileOperation : DownloadAndCacheFileOperation
|
||||||
|
{
|
||||||
|
private enum ESteps
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
CreateRequest,
|
||||||
|
CheckRequest,
|
||||||
|
VerifyBundleFile,
|
||||||
|
CacheBundleFile,
|
||||||
|
Done,
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly DefaultCacheFileSystem _fileSystem;
|
||||||
|
private readonly PackageBundle _bundle;
|
||||||
|
private readonly string _tempFilePath;
|
||||||
|
private bool _enableResume = false;
|
||||||
|
private long _fileOriginLength = 0;
|
||||||
|
private IDownloadRequest _request;
|
||||||
|
private VerifyTempFileOperation _verifyOperation;
|
||||||
|
private ESteps _steps = ESteps.None;
|
||||||
|
|
||||||
|
internal DownloadAndCacheRemoteFileOperation(DefaultCacheFileSystem fileSystem, PackageBundle bundle, string url) : base(url)
|
||||||
|
{
|
||||||
|
_fileSystem = fileSystem;
|
||||||
|
_bundle = bundle;
|
||||||
|
_tempFilePath = _fileSystem.GetTempFilePath(_bundle);
|
||||||
|
}
|
||||||
|
internal override void InternalStart()
|
||||||
|
{
|
||||||
|
_steps = ESteps.CreateRequest;
|
||||||
|
}
|
||||||
|
internal override void InternalUpdate()
|
||||||
|
{
|
||||||
|
if (_steps == ESteps.None || _steps == ESteps.Done)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 创建下载请求
|
||||||
|
if (_steps == ESteps.CreateRequest)
|
||||||
|
{
|
||||||
|
FileUtility.CreateFileDirectory(_tempFilePath);
|
||||||
|
|
||||||
|
_enableResume = _bundle.FileSize >= _fileSystem.ResumeDownloadMinimumSize;
|
||||||
|
if (_enableResume)
|
||||||
|
{
|
||||||
|
_request = CreateResumeRequest();
|
||||||
|
_request.SendRequest();
|
||||||
|
_steps = ESteps.CheckRequest;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_request = CreateNormalRequest();
|
||||||
|
_request.SendRequest();
|
||||||
|
_steps = ESteps.CheckRequest;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检测下载结果
|
||||||
|
if (_steps == ESteps.CheckRequest)
|
||||||
|
{
|
||||||
|
DownloadProgress = _request.DownloadProgress;
|
||||||
|
DownloadedBytes = _fileOriginLength + _request.DownloadedBytes;
|
||||||
|
Progress = DownloadProgress;
|
||||||
|
if (_request.IsDone == false)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 检查网络错误
|
||||||
|
if (_request.Status == EDownloadRequestStatus.Succeed)
|
||||||
|
{
|
||||||
|
_steps = ESteps.VerifyBundleFile;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_steps = ESteps.Done;
|
||||||
|
Status = EOperationStatus.Failed;
|
||||||
|
Error = _request.Error;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在遇到特殊错误的时候删除文件
|
||||||
|
if (_enableResume)
|
||||||
|
ClearTempFileWhenError(_request.HttpCode);
|
||||||
|
|
||||||
|
// 最终释放请求器
|
||||||
|
_request.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证下载结果
|
||||||
|
if (_steps == ESteps.VerifyBundleFile)
|
||||||
|
{
|
||||||
|
if (_verifyOperation == null)
|
||||||
|
{
|
||||||
|
var element = new TempFileElement(_tempFilePath, _bundle.FileCRC, _bundle.FileSize);
|
||||||
|
_verifyOperation = new VerifyTempFileOperation(element);
|
||||||
|
_verifyOperation.StartOperation();
|
||||||
|
AddChildOperation(_verifyOperation);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsWaitForAsyncComplete)
|
||||||
|
_verifyOperation.WaitForAsyncComplete();
|
||||||
|
|
||||||
|
_verifyOperation.UpdateOperation();
|
||||||
|
if (_verifyOperation.IsDone == false)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_verifyOperation.Status == EOperationStatus.Succeed)
|
||||||
|
{
|
||||||
|
_steps = ESteps.CacheBundleFile;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_steps = ESteps.Done;
|
||||||
|
Status = EOperationStatus.Failed;
|
||||||
|
Error = _verifyOperation.Error;
|
||||||
|
|
||||||
|
// 注意:验证失败后直接删除文件
|
||||||
|
if (File.Exists(_tempFilePath))
|
||||||
|
File.Delete(_tempFilePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 缓存文件
|
||||||
|
if (_steps == ESteps.CacheBundleFile)
|
||||||
|
{
|
||||||
|
if (_fileSystem.WriteCacheBundleFile(_bundle, _tempFilePath))
|
||||||
|
{
|
||||||
|
_steps = ESteps.Done;
|
||||||
|
Status = EOperationStatus.Succeed;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_steps = ESteps.Done;
|
||||||
|
Status = EOperationStatus.Failed;
|
||||||
|
Error = $"{_fileSystem.GetType().FullName} failed to write file !";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注意:缓存完成后直接删除临时文件
|
||||||
|
if (File.Exists(_tempFilePath))
|
||||||
|
File.Delete(_tempFilePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
internal override void InternalAbort()
|
||||||
|
{
|
||||||
|
if (_request != null)
|
||||||
|
_request.AbortRequest();
|
||||||
|
}
|
||||||
|
internal override void InternalWaitForAsyncComplete()
|
||||||
|
{
|
||||||
|
if (_steps != ESteps.Done)
|
||||||
|
{
|
||||||
|
// 注意:不中断下载任务,保持后台继续下载
|
||||||
|
YooLogger.Error($"Try load bundle {_bundle.BundleName} from remote : {URL} !");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IDownloadRequest CreateResumeRequest()
|
||||||
|
{
|
||||||
|
// 获取下载起始位置
|
||||||
|
if (File.Exists(_tempFilePath))
|
||||||
|
{
|
||||||
|
FileInfo fileInfo = new FileInfo(_tempFilePath);
|
||||||
|
if (fileInfo.Length >= _bundle.FileSize)
|
||||||
|
{
|
||||||
|
File.Delete(_tempFilePath);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_fileOriginLength = fileInfo.Length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int watchdogTime = _fileSystem.DownloadWatchDogTime;
|
||||||
|
int timeout = 0; //注意:文件下载不做超时检测
|
||||||
|
bool appendToFile = true;
|
||||||
|
bool removeFileOnAbort = false;
|
||||||
|
long resumeFromBytes = _fileOriginLength;
|
||||||
|
var args = new DownloadFileRequestArgs(URL, _tempFilePath, timeout, watchdogTime, appendToFile, removeFileOnAbort, resumeFromBytes);
|
||||||
|
return _fileSystem.DownloadBackend.CreateFileRequest(args);
|
||||||
|
}
|
||||||
|
private IDownloadRequest CreateNormalRequest()
|
||||||
|
{
|
||||||
|
// 删除历史缓存文件
|
||||||
|
if (File.Exists(_tempFilePath))
|
||||||
|
File.Delete(_tempFilePath);
|
||||||
|
|
||||||
|
int watchdogTime = _fileSystem.DownloadWatchDogTime;
|
||||||
|
int timeout = 0; //注意:文件下载不做超时检测
|
||||||
|
var args = new DownloadFileRequestArgs(URL, _tempFilePath, timeout, watchdogTime);
|
||||||
|
return _fileSystem.DownloadBackend.CreateFileRequest(args);
|
||||||
|
}
|
||||||
|
private void ClearTempFileWhenError(long httpCode)
|
||||||
|
{
|
||||||
|
if (_fileSystem.ResumeDownloadResponseCodes == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
//说明:如果遇到以下错误返回码,验证失败直接删除文件
|
||||||
|
if (_fileSystem.ResumeDownloadResponseCodes.Contains(httpCode))
|
||||||
|
{
|
||||||
|
if (File.Exists(_tempFilePath))
|
||||||
|
File.Delete(_tempFilePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ad910a81855976e44b4f1f09051910b0
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,189 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace YooAsset
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 下载调度器
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 管理所有活跃的下载任务,控制并发数量。
|
||||||
|
/// </remarks>
|
||||||
|
internal class DownloadSchedulerOperation : AsyncOperationBase, IDisposable
|
||||||
|
{
|
||||||
|
private readonly DefaultCacheFileSystem _fileSystem;
|
||||||
|
private readonly Dictionary<string, DownloadAndCacheFileOperation> _downloaders = new Dictionary<string, DownloadAndCacheFileOperation>(1000);
|
||||||
|
private readonly List<string> _removeList = new List<string>(1000);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否已暂停
|
||||||
|
/// </summary>
|
||||||
|
public bool Paused { get; private set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前活跃的下载任务数
|
||||||
|
/// </summary>
|
||||||
|
public int ActiveDownloadCount { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前等待中的下载任务数
|
||||||
|
/// </summary>
|
||||||
|
public int PendingDownloadCount
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _downloaders.Count - ActiveDownloadCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构造下载中心
|
||||||
|
/// </summary>
|
||||||
|
public DownloadSchedulerOperation(DefaultCacheFileSystem fileSystem)
|
||||||
|
{
|
||||||
|
_fileSystem = fileSystem;
|
||||||
|
}
|
||||||
|
internal override void InternalStart()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
internal override void InternalUpdate()
|
||||||
|
{
|
||||||
|
// 驱动下载后台
|
||||||
|
_fileSystem.DownloadBackend.Update();
|
||||||
|
|
||||||
|
// 获取可移除的下载器集合
|
||||||
|
_removeList.Clear();
|
||||||
|
foreach (var valuePair in _downloaders)
|
||||||
|
{
|
||||||
|
var downloader = valuePair.Value;
|
||||||
|
downloader.UpdateOperation();
|
||||||
|
if (downloader.IsDone)
|
||||||
|
{
|
||||||
|
_removeList.Add(valuePair.Key);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注意:主动终止引用计数为零的下载任务
|
||||||
|
if (downloader.RefCount <= 0)
|
||||||
|
{
|
||||||
|
_removeList.Add(valuePair.Key);
|
||||||
|
downloader.AbortOperation();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除下载器
|
||||||
|
foreach (var key in _removeList)
|
||||||
|
{
|
||||||
|
if (_downloaders.TryGetValue(key, out var downloader))
|
||||||
|
{
|
||||||
|
RemoveChildOperation(downloader);
|
||||||
|
_downloaders.Remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 暂停时不启动新任务
|
||||||
|
if (Paused)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 最大并发数检测
|
||||||
|
ActiveDownloadCount = GetProcessingOperationCount();
|
||||||
|
if (ActiveDownloadCount != _downloaders.Count)
|
||||||
|
{
|
||||||
|
int maxConcurrency = _fileSystem.DownloadMaxConcurrency;
|
||||||
|
int maxRequestPerFrame = _fileSystem.DownloadMaxRequestPerFrame;
|
||||||
|
if (ActiveDownloadCount < maxConcurrency)
|
||||||
|
{
|
||||||
|
int startCount = maxConcurrency - ActiveDownloadCount;
|
||||||
|
if (startCount > maxRequestPerFrame)
|
||||||
|
startCount = maxRequestPerFrame;
|
||||||
|
|
||||||
|
foreach (var operationPair in _downloaders)
|
||||||
|
{
|
||||||
|
var operation = operationPair.Value;
|
||||||
|
if (operation.Status == EOperationStatus.None)
|
||||||
|
{
|
||||||
|
operation.StartOperation();
|
||||||
|
startCount--;
|
||||||
|
if (startCount <= 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
internal override string InternalGetDesc()
|
||||||
|
{
|
||||||
|
return $"{_fileSystem.GetType().FullName}";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 中止所有下载任务
|
||||||
|
/// </summary>
|
||||||
|
public void AbortAll()
|
||||||
|
{
|
||||||
|
foreach (var valuePair in _downloaders)
|
||||||
|
{
|
||||||
|
valuePair.Value.AbortOperation();
|
||||||
|
}
|
||||||
|
_downloaders.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 释放资源
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
AbortAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建下载任务
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bundle">资源包信息</param>
|
||||||
|
/// <param name="url">下载地址</param>
|
||||||
|
/// <returns>下载操作</returns>
|
||||||
|
public DownloadAndCacheFileOperation DownloadAndCacheFileAsync(PackageBundle bundle, string url)
|
||||||
|
{
|
||||||
|
// 查询旧的下载器
|
||||||
|
if (_downloaders.TryGetValue(bundle.BundleGUID, out var oldDownloader))
|
||||||
|
{
|
||||||
|
oldDownloader.Reference();
|
||||||
|
return oldDownloader;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建新的下载器
|
||||||
|
DownloadAndCacheFileOperation newDownloader;
|
||||||
|
bool isRequestLocalFile = DownloadSystemHelper.IsRequestLocalFile(url);
|
||||||
|
if (isRequestLocalFile)
|
||||||
|
{
|
||||||
|
newDownloader = new DownloadAndCacheLocalFileOperation(_fileSystem, bundle, url);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
newDownloader = new DownloadAndCacheRemoteFileOperation(_fileSystem, bundle, url);
|
||||||
|
}
|
||||||
|
|
||||||
|
AddChildOperation(newDownloader);
|
||||||
|
_downloaders.Add(bundle.BundleGUID, newDownloader);
|
||||||
|
newDownloader.Reference();
|
||||||
|
return newDownloader;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取正在进行中的下载器总数
|
||||||
|
/// </summary>
|
||||||
|
private int GetProcessingOperationCount()
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
foreach (var operationPair in _downloaders)
|
||||||
|
{
|
||||||
|
var operation = operationPair.Value;
|
||||||
|
if (operation.Status != EOperationStatus.None)
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 73441e829c36a36418f5d677e189e9f3
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
|
|
||||||
namespace YooAsset
|
|
||||||
{
|
|
||||||
internal abstract class UnityDownloadFileOperation : UnityWebRequestOperation
|
|
||||||
{
|
|
||||||
protected enum ESteps
|
|
||||||
{
|
|
||||||
None,
|
|
||||||
CreateRequest,
|
|
||||||
Download,
|
|
||||||
CopyLocalFile,
|
|
||||||
VerifyFile,
|
|
||||||
Done,
|
|
||||||
}
|
|
||||||
|
|
||||||
protected readonly DefaultCacheFileSystem _fileSystem;
|
|
||||||
protected readonly PackageBundle _bundle;
|
|
||||||
protected readonly string _tempFilePath;
|
|
||||||
|
|
||||||
private bool _watchDogInit = false;
|
|
||||||
private bool _watchDogAborted = false;
|
|
||||||
private ulong _lastDownloadBytes;
|
|
||||||
private double _lastGetDataTime;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 引用计数
|
|
||||||
/// </summary>
|
|
||||||
public int RefCount { private set; get; }
|
|
||||||
|
|
||||||
internal UnityDownloadFileOperation(DefaultCacheFileSystem fileSystem, PackageBundle bundle, string url) : base(url)
|
|
||||||
{
|
|
||||||
_fileSystem = fileSystem;
|
|
||||||
_bundle = bundle;
|
|
||||||
_tempFilePath = _fileSystem.GetTempFilePath(bundle);
|
|
||||||
}
|
|
||||||
internal override string InternalGetDesc()
|
|
||||||
{
|
|
||||||
return $"RefCount : {RefCount}";
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 更新看门狗监测
|
|
||||||
/// 说明:监控时间范围内,如果没有接收到任何下载数据,那么直接终止任务!
|
|
||||||
/// </summary>
|
|
||||||
protected void UpdateWatchDog()
|
|
||||||
{
|
|
||||||
if (_fileSystem.DownloadWatchDogTime == int.MaxValue)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (_watchDogAborted)
|
|
||||||
return;
|
|
||||||
|
|
||||||
#if UNITY_2020_3_OR_NEWER
|
|
||||||
double realtimeSinceStartup = UnityEngine.Time.realtimeSinceStartupAsDouble;
|
|
||||||
#else
|
|
||||||
double realtimeSinceStartup = UnityEngine.Time.realtimeSinceStartup;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (_watchDogInit == false)
|
|
||||||
{
|
|
||||||
_watchDogInit = true;
|
|
||||||
_lastDownloadBytes = 0;
|
|
||||||
_lastGetDataTime = realtimeSinceStartup;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_webRequest.downloadedBytes != _lastDownloadBytes)
|
|
||||||
{
|
|
||||||
_lastDownloadBytes = _webRequest.downloadedBytes;
|
|
||||||
_lastGetDataTime = realtimeSinceStartup;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
double deltaTime = realtimeSinceStartup - _lastGetDataTime;
|
|
||||||
if (deltaTime > _fileSystem.DownloadWatchDogTime)
|
|
||||||
{
|
|
||||||
_watchDogAborted = true;
|
|
||||||
InternalAbort(); //终止网络请求
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 减少引用计数
|
|
||||||
/// </summary>
|
|
||||||
public void Release()
|
|
||||||
{
|
|
||||||
RefCount--;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 增加引用计数
|
|
||||||
/// </summary>
|
|
||||||
public void Reference()
|
|
||||||
{
|
|
||||||
RefCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 40bb5e9391f413c42ae70e48ca90c4b7
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: c68640bb1d36552469024324e3357bc2
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@@ -1,124 +0,0 @@
|
|||||||
using System.IO;
|
|
||||||
using UnityEngine;
|
|
||||||
using UnityEngine.Networking;
|
|
||||||
|
|
||||||
namespace YooAsset
|
|
||||||
{
|
|
||||||
internal sealed class UnityDownloadNormalFileOperation : UnityDownloadFileOperation
|
|
||||||
{
|
|
||||||
private VerifyTempFileOperation _verifyOperation;
|
|
||||||
private ESteps _steps = ESteps.None;
|
|
||||||
|
|
||||||
internal UnityDownloadNormalFileOperation(DefaultCacheFileSystem fileSystem, PackageBundle bundle, string url)
|
|
||||||
: base(fileSystem, bundle, url)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
internal override void InternalStart()
|
|
||||||
{
|
|
||||||
_steps = ESteps.CreateRequest;
|
|
||||||
}
|
|
||||||
internal override void InternalUpdate()
|
|
||||||
{
|
|
||||||
if (_steps == ESteps.None || _steps == ESteps.Done)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// 创建下载器
|
|
||||||
if (_steps == ESteps.CreateRequest)
|
|
||||||
{
|
|
||||||
FileUtility.CreateFileDirectory(_tempFilePath);
|
|
||||||
if (File.Exists(_tempFilePath))
|
|
||||||
File.Delete(_tempFilePath);
|
|
||||||
|
|
||||||
CreateWebRequest();
|
|
||||||
_steps = ESteps.Download;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检测下载结果
|
|
||||||
if (_steps == ESteps.Download)
|
|
||||||
{
|
|
||||||
DownloadProgress = _webRequest.downloadProgress;
|
|
||||||
DownloadedBytes = (long)_webRequest.downloadedBytes;
|
|
||||||
Progress = DownloadProgress;
|
|
||||||
|
|
||||||
UpdateWatchDog();
|
|
||||||
if (_webRequest.isDone == false)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// 检查网络错误
|
|
||||||
if (CheckRequestResult())
|
|
||||||
{
|
|
||||||
_steps = ESteps.VerifyFile;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_steps = ESteps.Done;
|
|
||||||
Status = EOperationStatus.Failed;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 注意:最终释放请求器
|
|
||||||
DisposeRequest();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证下载文件
|
|
||||||
if (_steps == ESteps.VerifyFile)
|
|
||||||
{
|
|
||||||
if (_verifyOperation == null)
|
|
||||||
{
|
|
||||||
var element = new TempFileElement(_tempFilePath, _bundle.FileCRC, _bundle.FileSize);
|
|
||||||
_verifyOperation = new VerifyTempFileOperation(element);
|
|
||||||
_verifyOperation.StartOperation();
|
|
||||||
AddChildOperation(_verifyOperation);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IsWaitForAsyncComplete)
|
|
||||||
_verifyOperation.WaitForAsyncComplete();
|
|
||||||
|
|
||||||
_verifyOperation.UpdateOperation();
|
|
||||||
if (_verifyOperation.IsDone == false)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (_verifyOperation.Status == EOperationStatus.Succeed)
|
|
||||||
{
|
|
||||||
if (_fileSystem.WriteCacheBundleFile(_bundle, _tempFilePath))
|
|
||||||
{
|
|
||||||
_steps = ESteps.Done;
|
|
||||||
Status = EOperationStatus.Succeed;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_steps = ESteps.Done;
|
|
||||||
Status = EOperationStatus.Failed;
|
|
||||||
Error = $"{_fileSystem.GetType().FullName} failed to write file ! {_tempFilePath}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_steps = ESteps.Done;
|
|
||||||
Status = EOperationStatus.Failed;
|
|
||||||
Error = _verifyOperation.Error;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 注意:验证完成后直接删除文件
|
|
||||||
if (File.Exists(_tempFilePath))
|
|
||||||
File.Delete(_tempFilePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
internal override void InternalWaitForAsyncComplete()
|
|
||||||
{
|
|
||||||
if (_steps != ESteps.Done)
|
|
||||||
{
|
|
||||||
YooLogger.Error($"Try load bundle {_bundle.BundleName} from remote : {_requestURL} !");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CreateWebRequest()
|
|
||||||
{
|
|
||||||
DownloadHandlerFile handler = new DownloadHandlerFile(_tempFilePath);
|
|
||||||
handler.removeFileOnAbort = true;
|
|
||||||
_webRequest = DownloadSystemHelper.NewUnityWebRequestGet(_requestURL);
|
|
||||||
_webRequest.downloadHandler = handler;
|
|
||||||
_webRequest.disposeDownloadHandlerOnDispose = true;
|
|
||||||
_webRequest.SendWebRequest();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 759967d543776a0469b625eff171d235
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
using System.IO;
|
|
||||||
using UnityEngine;
|
|
||||||
using UnityEngine.Networking;
|
|
||||||
|
|
||||||
namespace YooAsset
|
|
||||||
{
|
|
||||||
internal sealed class UnityDownloadResumeFileOperation : UnityDownloadFileOperation
|
|
||||||
{
|
|
||||||
private VerifyTempFileOperation _verifyOperation;
|
|
||||||
private long _fileOriginLength = 0;
|
|
||||||
private ESteps _steps = ESteps.None;
|
|
||||||
|
|
||||||
internal UnityDownloadResumeFileOperation(DefaultCacheFileSystem fileSystem, PackageBundle bundle, string url)
|
|
||||||
: base(fileSystem, bundle, url)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
internal override void InternalStart()
|
|
||||||
{
|
|
||||||
_steps = ESteps.CreateRequest;
|
|
||||||
}
|
|
||||||
internal override void InternalUpdate()
|
|
||||||
{
|
|
||||||
if (_steps == ESteps.None || _steps == ESteps.Done)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// 创建下载器
|
|
||||||
if (_steps == ESteps.CreateRequest)
|
|
||||||
{
|
|
||||||
FileUtility.CreateFileDirectory(_tempFilePath);
|
|
||||||
|
|
||||||
// 获取下载起始位置
|
|
||||||
_fileOriginLength = 0;
|
|
||||||
long fileBeginLength = -1;
|
|
||||||
if (File.Exists(_tempFilePath))
|
|
||||||
{
|
|
||||||
FileInfo fileInfo = new FileInfo(_tempFilePath);
|
|
||||||
if (fileInfo.Length >= _bundle.FileSize)
|
|
||||||
{
|
|
||||||
File.Delete(_tempFilePath);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
fileBeginLength = fileInfo.Length;
|
|
||||||
_fileOriginLength = fileBeginLength;
|
|
||||||
DownloadedBytes = _fileOriginLength;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CreateWebRequest(fileBeginLength);
|
|
||||||
_steps = ESteps.Download;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检测下载结果
|
|
||||||
if (_steps == ESteps.Download)
|
|
||||||
{
|
|
||||||
DownloadProgress = _webRequest.downloadProgress;
|
|
||||||
DownloadedBytes = _fileOriginLength + (long)_webRequest.downloadedBytes;
|
|
||||||
Progress = DownloadProgress;
|
|
||||||
|
|
||||||
UpdateWatchDog();
|
|
||||||
if (_webRequest.isDone == false)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// 检查网络错误
|
|
||||||
if (CheckRequestResult())
|
|
||||||
{
|
|
||||||
_steps = ESteps.VerifyFile;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_steps = ESteps.Done;
|
|
||||||
Status = EOperationStatus.Failed;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 在遇到特殊错误的时候删除文件
|
|
||||||
ClearTempFileWhenError();
|
|
||||||
|
|
||||||
// 注意:最终释放请求器
|
|
||||||
DisposeRequest();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证下载文件
|
|
||||||
if (_steps == ESteps.VerifyFile)
|
|
||||||
{
|
|
||||||
if (_verifyOperation == null)
|
|
||||||
{
|
|
||||||
var element = new TempFileElement(_tempFilePath, _bundle.FileCRC, _bundle.FileSize);
|
|
||||||
_verifyOperation = new VerifyTempFileOperation(element);
|
|
||||||
_verifyOperation.StartOperation();
|
|
||||||
AddChildOperation(_verifyOperation);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IsWaitForAsyncComplete)
|
|
||||||
_verifyOperation.WaitForAsyncComplete();
|
|
||||||
|
|
||||||
_verifyOperation.UpdateOperation();
|
|
||||||
if (_verifyOperation.IsDone == false)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (_verifyOperation.Status == EOperationStatus.Succeed)
|
|
||||||
{
|
|
||||||
if (_fileSystem.WriteCacheBundleFile(_bundle, _tempFilePath))
|
|
||||||
{
|
|
||||||
_steps = ESteps.Done;
|
|
||||||
Status = EOperationStatus.Succeed;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_steps = ESteps.Done;
|
|
||||||
Status = EOperationStatus.Failed;
|
|
||||||
Error = $"{_fileSystem.GetType().FullName} failed to write file !";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_steps = ESteps.Done;
|
|
||||||
Status = EOperationStatus.Failed;
|
|
||||||
Error = _verifyOperation.Error;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 注意:验证完成后直接删除文件
|
|
||||||
if (File.Exists(_tempFilePath))
|
|
||||||
File.Delete(_tempFilePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
internal override void InternalWaitForAsyncComplete()
|
|
||||||
{
|
|
||||||
if (_steps != ESteps.Done)
|
|
||||||
{
|
|
||||||
YooLogger.Error($"Try load bundle {_bundle.BundleName} from remote : {_requestURL} !");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ClearTempFileWhenError()
|
|
||||||
{
|
|
||||||
if (_fileSystem.ResumeDownloadResponseCodes == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
//说明:如果遇到以下错误返回码,验证失败直接删除文件
|
|
||||||
if (_fileSystem.ResumeDownloadResponseCodes.Contains(HttpCode))
|
|
||||||
{
|
|
||||||
if (File.Exists(_tempFilePath))
|
|
||||||
File.Delete(_tempFilePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private void CreateWebRequest(long fileBeginLength)
|
|
||||||
{
|
|
||||||
var handler = new DownloadHandlerFile(_tempFilePath, true);
|
|
||||||
handler.removeFileOnAbort = false;
|
|
||||||
_webRequest = DownloadSystemHelper.NewUnityWebRequestGet(_requestURL);
|
|
||||||
_webRequest.downloadHandler = handler;
|
|
||||||
_webRequest.disposeDownloadHandlerOnDispose = true;
|
|
||||||
if (fileBeginLength > 0)
|
|
||||||
_webRequest.SetRequestHeader("Range", $"bytes={fileBeginLength}-");
|
|
||||||
_webRequest.SendWebRequest();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 9bfb785912326f942a31dc77762eb16a
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@@ -76,7 +76,7 @@ namespace YooAsset
|
|||||||
if (IsDone)
|
if (IsDone)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// 短暂休眠避免完全卡死
|
//TODO 短暂休眠避免完全卡死
|
||||||
System.Threading.Thread.Sleep(1);
|
System.Threading.Thread.Sleep(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,791 @@
|
|||||||
|
# DefaultCacheFileSystem 缓存文件系统
|
||||||
|
|
||||||
|
## 模块概述
|
||||||
|
|
||||||
|
DefaultCacheFileSystem 是 YooAsset 的**缓存文件系统**,负责管理从远程服务器下载并缓存到本地沙盒的资源文件。该文件系统是联机运行模式(HostPlayMode)的核心组件,提供完整的下载、验证、缓存和加载功能。
|
||||||
|
|
||||||
|
### 核心特性
|
||||||
|
|
||||||
|
- **智能缓存管理**:基于 GUID 的文件索引,支持增量更新
|
||||||
|
- **断点续传**:大文件下载支持从断点继续
|
||||||
|
- **多线程验证**:后台线程验证文件完整性,不阻塞主线程
|
||||||
|
- **并发下载**:可配置的下载并发数和请求速率
|
||||||
|
- **覆盖安装检测**:App 版本变更时自动清理过期缓存
|
||||||
|
- **加密支持**:支持加密资源包的解密加载
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 设计目标
|
||||||
|
|
||||||
|
| 目标 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| **高性能** | 多线程验证、并发下载、路径缓存优化 |
|
||||||
|
| **高可靠** | CRC/Hash 验证、损坏文件自动清理、加载失败重试 |
|
||||||
|
| **可扩展** | 支持自定义解密服务、远程服务、本地拷贝服务 |
|
||||||
|
| **易配置** | 丰富的参数配置,适应不同网络环境 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 文件结构
|
||||||
|
|
||||||
|
```
|
||||||
|
DefaultCacheFileSystem/
|
||||||
|
├── DefaultCacheFileSystem.cs # 文件系统主类
|
||||||
|
├── DefaultCacheFileSystemDefine.cs # 常量定义
|
||||||
|
├── EOverwriteInstallClearMode.cs # 覆盖安装清理模式枚举
|
||||||
|
├── ApplicationFootPrint.cs # 应用版本足迹
|
||||||
|
├── Elements/ # 元素类
|
||||||
|
│ ├── RecordFileElement.cs # 缓存文件记录元素
|
||||||
|
│ ├── TempFileElement.cs # 临时文件元素
|
||||||
|
│ └── VerifyFileElement.cs # 验证文件元素
|
||||||
|
└── Operation/ # 操作类
|
||||||
|
├── DCFSInitializeOperation.cs # 初始化操作
|
||||||
|
├── DCFSRequestPackageVersionOperation.cs # 请求版本操作
|
||||||
|
├── DCFSLoadPackageManifestOperation.cs # 加载清单操作
|
||||||
|
├── DCFSLoadBundleOperation.cs # 加载资源包操作
|
||||||
|
└── internal/ # 内部操作类
|
||||||
|
├── SearchCacheFilesOperation.cs # 搜索缓存文件
|
||||||
|
├── VerifyCacheFilesOperation.cs # 验证缓存文件
|
||||||
|
├── VerifyTempFileOperation.cs # 验证临时文件
|
||||||
|
├── DownloadPackageHashOperation.cs # 下载哈希文件
|
||||||
|
├── DownloadPackageManifestOperation.cs # 下载清单文件
|
||||||
|
├── DownloadPackageBundleOperation.cs # 下载资源包
|
||||||
|
├── LoadCachePackageHashOperation.cs # 加载缓存哈希
|
||||||
|
├── LoadCachePackageManifestOperation.cs # 加载缓存清单
|
||||||
|
├── ClearAllCacheBundleFilesOperation.cs # 清理所有缓存
|
||||||
|
├── ClearUnusedCacheBundleFilesOperation.cs # 清理未使用缓存
|
||||||
|
├── ClearCacheBundleFilesByTagsOperaiton.cs # 按标签清理
|
||||||
|
├── ClearCacheBundleFilesByLocationsOperaiton.cs # 按位置清理
|
||||||
|
├── ClearAllCacheManifestFilesOperation.cs # 清理所有清单
|
||||||
|
├── ClearUnusedCacheManifestFilesOperation.cs # 清理未使用清单
|
||||||
|
└── Scheduler/ # 下载调度器
|
||||||
|
├── DownloadSchedulerOperation.cs # 下载调度器
|
||||||
|
├── DownloadAndCacheFileOperation.cs # 下载并缓存基类
|
||||||
|
├── DownloadAndCacheRemoteFileOperation.cs # 远程文件下载
|
||||||
|
└── DownloadAndCacheLocalFileOperation.cs # 本地文件拷贝
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 核心类说明
|
||||||
|
|
||||||
|
### DefaultCacheFileSystem
|
||||||
|
|
||||||
|
缓存文件系统的主类,实现 `IFileSystem` 接口。
|
||||||
|
|
||||||
|
#### 基本属性
|
||||||
|
|
||||||
|
| 属性 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `PackageName` | `string` | 包裹名称 |
|
||||||
|
| `FileRoot` | `string` | 缓存根目录 |
|
||||||
|
| `FileCount` | `int` | 已缓存文件数量 |
|
||||||
|
| `DownloadBackend` | `IDownloadBackend` | 下载后台接口 |
|
||||||
|
| `DownloadScheduler` | `DownloadSchedulerOperation` | 下载调度器 |
|
||||||
|
|
||||||
|
#### 自定义参数
|
||||||
|
|
||||||
|
| 参数 | 类型 | 默认值 | 说明 |
|
||||||
|
|------|------|--------|------|
|
||||||
|
| `RemoteServices` | `IRemoteServices` | - | 远程服务接口(必需) |
|
||||||
|
| `InstallClearMode` | `EOverwriteInstallClearMode` | `ClearAllManifestFiles` | 覆盖安装缓存清理模式 |
|
||||||
|
| `FileVerifyLevel` | `EFileVerifyLevel` | `Middle` | 初始化时文件校验级别 |
|
||||||
|
| `FileVerifyMaxConcurrency` | `int` | `32` | 文件校验最大并发数(1-256) |
|
||||||
|
| `AppendFileExtension` | `bool` | `false` | 数据文件追加文件扩展名 |
|
||||||
|
| `DisableOnDemandDownload` | `bool` | `false` | 禁用边玩边下机制 |
|
||||||
|
| `DownloadMaxConcurrency` | `int` | `10` | 最大并发下载数(1-64) |
|
||||||
|
| `DownloadMaxRequestPerFrame` | `int` | `5` | 每帧最大请求数(1-20) |
|
||||||
|
| `DownloadWatchDogTime` | `int` | `0` | 下载看门狗超时时间(秒) |
|
||||||
|
| `ResumeDownloadMinimumSize` | `long` | `long.MaxValue` | 启用断点续传的最小文件大小 |
|
||||||
|
| `ResumeDownloadResponseCodes` | `List<long>` | `null` | 断点续传关注的HTTP错误码 |
|
||||||
|
| `DecryptionServices` | `IDecryptionServices` | `null` | 解密服务接口 |
|
||||||
|
| `ManifestServices` | `IManifestRestoreServices` | `null` | 清单服务接口 |
|
||||||
|
| `CopyLocalFileServices` | `ICopyLocalFileServices` | `null` | 本地文件拷贝服务 |
|
||||||
|
|
||||||
|
#### 核心方法
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 生命周期
|
||||||
|
void OnCreate(string packageName, string packageRoot);
|
||||||
|
void OnDestroy();
|
||||||
|
void SetParameter(string name, object value);
|
||||||
|
|
||||||
|
// 异步操作
|
||||||
|
FSInitializeFileSystemOperation InitializeFileSystemAsync();
|
||||||
|
FSRequestPackageVersionOperation RequestPackageVersionAsync(bool appendTimeTicks, int timeout);
|
||||||
|
FSLoadPackageManifestOperation LoadPackageManifestAsync(string packageVersion, int timeout);
|
||||||
|
FSLoadBundleOperation LoadBundleFile(PackageBundle bundle);
|
||||||
|
FSDownloadFileOperation DownloadFileAsync(PackageBundle bundle, DownloadFileOptions options);
|
||||||
|
FSClearCacheFilesOperation ClearCacheFilesAsync(PackageManifest manifest, ClearCacheFilesOptions options);
|
||||||
|
|
||||||
|
// 文件查询
|
||||||
|
bool Belong(PackageBundle bundle); // 始终返回 true(保底加载)
|
||||||
|
bool Exists(PackageBundle bundle); // 检查文件是否已缓存
|
||||||
|
bool NeedDownload(PackageBundle bundle); // 检查是否需要下载
|
||||||
|
bool NeedUnpack(PackageBundle bundle); // 始终返回 false
|
||||||
|
bool NeedImport(PackageBundle bundle); // 检查是否需要导入
|
||||||
|
|
||||||
|
// 文件访问
|
||||||
|
string GetBundleFilePath(PackageBundle bundle);
|
||||||
|
byte[] ReadBundleFileData(PackageBundle bundle);
|
||||||
|
string ReadBundleFileText(PackageBundle bundle);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 缓存目录结构
|
||||||
|
|
||||||
|
```
|
||||||
|
{CacheRoot}/{PackageName}/
|
||||||
|
├── BundleFiles/ # 资源包文件目录
|
||||||
|
│ ├── {Hash[0:2]}/ # 哈希前两位分组(256个目录)
|
||||||
|
│ │ ├── {BundleGUID}/ # 资源包 GUID 目录
|
||||||
|
│ │ │ ├── __data # 数据文件(或 __data.bundle)
|
||||||
|
│ │ │ └── __info # 信息文件(CRC + Size)
|
||||||
|
│ │ └── ...
|
||||||
|
│ └── ...
|
||||||
|
├── ManifestFiles/ # 清单文件目录
|
||||||
|
│ ├── {PackageName}_{Version}.bytes # 清单二进制文件
|
||||||
|
│ ├── {PackageName}_{Version}.hash # 清单哈希文件
|
||||||
|
│ └── __app_footprint.txt # 应用版本足迹文件
|
||||||
|
└── TempFiles/ # 临时文件目录
|
||||||
|
├── {BundleGUID} # 下载中的临时文件
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 信息文件格式(__info)
|
||||||
|
|
||||||
|
```
|
||||||
|
| 字段 | 类型 | 大小 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| DataFileCRC | uint32 | 4 bytes | 数据文件 CRC |
|
||||||
|
| DataFileSize | int64 | 8 bytes | 数据文件大小 |
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 操作类说明
|
||||||
|
|
||||||
|
### DCFSInitializeOperation
|
||||||
|
|
||||||
|
初始化操作,执行完整的缓存系统初始化流程。
|
||||||
|
|
||||||
|
```
|
||||||
|
状态流程:
|
||||||
|
CheckAppFootPrint
|
||||||
|
├── 版本相同 → 继续
|
||||||
|
└── 版本不同 → 根据 InstallClearMode 清理缓存
|
||||||
|
↓
|
||||||
|
SearchCacheFiles
|
||||||
|
└── SearchCacheFilesOperation
|
||||||
|
└── 遍历 BundleFiles 目录
|
||||||
|
└── 收集需要验证的文件
|
||||||
|
↓
|
||||||
|
VerifyCacheFiles
|
||||||
|
└── VerifyCacheFilesOperation(多线程)
|
||||||
|
├── 验证成功 → 记录到 _records
|
||||||
|
└── 验证失败 → 删除损坏文件
|
||||||
|
↓
|
||||||
|
CreateDownloadScheduler
|
||||||
|
└── 创建 DownloadSchedulerOperation
|
||||||
|
↓
|
||||||
|
Done → Status = Succeed
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 状态机枚举
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
private enum ESteps
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
CheckAppFootPrint, // 检查应用版本足迹
|
||||||
|
SearchCacheFiles, // 搜索缓存文件
|
||||||
|
VerifyCacheFiles, // 验证缓存文件
|
||||||
|
CreateDownloadScheduler,// 创建下载调度器
|
||||||
|
Done // 完成
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### DCFSLoadAssetBundleOperation
|
||||||
|
|
||||||
|
加载 AssetBundle 操作,支持按需下载和多重容错机制。
|
||||||
|
|
||||||
|
```
|
||||||
|
状态流程:
|
||||||
|
CheckExist
|
||||||
|
├── 已缓存 → LoadAssetBundle
|
||||||
|
└── 未缓存 → 检查 DisableOnDemandDownload
|
||||||
|
├── 禁用 → Failed
|
||||||
|
└── 启用 → DownloadFile
|
||||||
|
↓
|
||||||
|
DownloadFile
|
||||||
|
└── DownloadFileAsync()
|
||||||
|
├── 下载成功 → LoadAssetBundle
|
||||||
|
└── 下载失败 → Failed
|
||||||
|
↓
|
||||||
|
LoadAssetBundle
|
||||||
|
├── 未加密 → AssetBundle.LoadFromFile[Async]
|
||||||
|
└── 已加密 → DecryptionServices.LoadAssetBundle[Async]
|
||||||
|
↓
|
||||||
|
CheckResult
|
||||||
|
├── 加载成功 → AssetBundleResult → Succeed
|
||||||
|
└── 加载失败 → 验证文件完整性
|
||||||
|
├── 验证通过 → LoadFromMemory 重试
|
||||||
|
└── 验证失败 → 删除损坏文件 → Failed
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 移动平台容错机制
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 注意:在安卓移动平台,华为和三星真机上有极小概率加载资源包失败。
|
||||||
|
// 说明:大多数情况在首次安装下载资源到沙盒内,游戏过程中切换到后台再回到游戏内有很大概率触发!
|
||||||
|
string filePath = _fileSystem.GetCacheBundleFileLoadPath(_bundle);
|
||||||
|
byte[] fileData = FileUtility.ReadAllBytes(filePath);
|
||||||
|
if (fileData != null && fileData.Length > 0)
|
||||||
|
{
|
||||||
|
_assetBundle = AssetBundle.LoadFromMemory(fileData);
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### DCFSLoadRawBundleOperation
|
||||||
|
|
||||||
|
加载原生资源包操作,处理文件格式变更场景。
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 注意:缓存的原生文件的格式,可能会在业务端根据需求发生变动!
|
||||||
|
// 注意:这里需要校验文件格式,如果不一致对本地文件进行修正!
|
||||||
|
if (File.Exists(filePath) == false)
|
||||||
|
{
|
||||||
|
var recordFileElement = _fileSystem.GetRecordFileElement(_bundle);
|
||||||
|
File.Move(recordFileElement.DataFilePath, filePath);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 下载调度器
|
||||||
|
|
||||||
|
### DownloadSchedulerOperation
|
||||||
|
|
||||||
|
管理所有活跃的下载任务,控制并发数量。
|
||||||
|
|
||||||
|
#### 核心属性
|
||||||
|
|
||||||
|
| 属性 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `Paused` | `bool` | 是否已暂停 |
|
||||||
|
| `ActiveDownloadCount` | `int` | 当前活跃的下载任务数 |
|
||||||
|
| `PendingDownloadCount` | `int` | 当前等待中的下载任务数 |
|
||||||
|
|
||||||
|
#### 工作原理
|
||||||
|
|
||||||
|
```
|
||||||
|
InternalUpdate()
|
||||||
|
│
|
||||||
|
├── 1. 驱动下载后台 _fileSystem.DownloadBackend.Update()
|
||||||
|
│
|
||||||
|
├── 2. 遍历下载器集合
|
||||||
|
│ ├── 已完成 → 加入移除列表
|
||||||
|
│ └── RefCount <= 0 → 中止并移除
|
||||||
|
│
|
||||||
|
├── 3. 移除已完成/中止的下载器
|
||||||
|
│
|
||||||
|
└── 4. 启动新下载任务(如未暂停)
|
||||||
|
├── 计算可启动数量 = min(maxConcurrency - active, maxRequestPerFrame)
|
||||||
|
└── 启动等待中的下载器
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 引用计数机制
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 查询旧的下载器(复用)
|
||||||
|
if (_downloaders.TryGetValue(bundle.BundleGUID, out var oldDownloader))
|
||||||
|
{
|
||||||
|
oldDownloader.Reference(); // 引用计数 +1
|
||||||
|
return oldDownloader;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建新的下载器
|
||||||
|
DownloadAndCacheFileOperation newDownloader;
|
||||||
|
// ...
|
||||||
|
newDownloader.Reference(); // 引用计数 +1
|
||||||
|
```
|
||||||
|
|
||||||
|
### DownloadAndCacheRemoteFileOperation
|
||||||
|
|
||||||
|
远程文件下载操作,支持断点续传。
|
||||||
|
|
||||||
|
```
|
||||||
|
状态流程:
|
||||||
|
CreateRequest
|
||||||
|
├── 文件大小 >= ResumeDownloadMinimumSize
|
||||||
|
│ └── CreateResumeRequest(断点续传)
|
||||||
|
└── 文件大小 < ResumeDownloadMinimumSize
|
||||||
|
└── CreateNormalRequest(普通下载)
|
||||||
|
↓
|
||||||
|
CheckRequest
|
||||||
|
├── 下载成功 → VerifyBundleFile
|
||||||
|
└── 下载失败 → ClearTempFileWhenError → Failed
|
||||||
|
↓
|
||||||
|
VerifyBundleFile
|
||||||
|
└── VerifyTempFileOperation(多线程验证)
|
||||||
|
├── 验证成功 → CacheBundleFile
|
||||||
|
└── 验证失败 → 删除临时文件 → Failed
|
||||||
|
↓
|
||||||
|
CacheBundleFile
|
||||||
|
└── WriteCacheBundleFile()
|
||||||
|
├── 成功 → 删除临时文件 → Succeed
|
||||||
|
└── 失败 → Failed
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 断点续传实现
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
private IDownloadRequest CreateResumeRequest()
|
||||||
|
{
|
||||||
|
// 获取下载起始位置
|
||||||
|
if (File.Exists(_tempFilePath))
|
||||||
|
{
|
||||||
|
FileInfo fileInfo = new FileInfo(_tempFilePath);
|
||||||
|
if (fileInfo.Length >= _bundle.FileSize)
|
||||||
|
{
|
||||||
|
File.Delete(_tempFilePath); // 文件已完整,删除重下
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_fileOriginLength = fileInfo.Length; // 记录已下载大小
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var args = new DownloadFileRequestArgs(
|
||||||
|
URL, _tempFilePath, timeout, watchdogTime,
|
||||||
|
appendToFile: true, // 追加写入
|
||||||
|
removeFileOnAbort: false, // 中止时保留文件
|
||||||
|
resumeFromBytes: _fileOriginLength // 断点位置
|
||||||
|
);
|
||||||
|
return _fileSystem.DownloadBackend.CreateFileRequest(args);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 缓存验证系统
|
||||||
|
|
||||||
|
### VerifyCacheFilesOperation
|
||||||
|
|
||||||
|
多线程缓存文件验证,在初始化时执行。
|
||||||
|
|
||||||
|
#### 验证流程
|
||||||
|
|
||||||
|
```
|
||||||
|
InitVerify
|
||||||
|
├── 获取系统线程池信息
|
||||||
|
└── 计算实际并发数 = min(threads, FileVerifyMaxConcurrency)
|
||||||
|
↓
|
||||||
|
UpdateVerify(循环)
|
||||||
|
├── 检测已完成的验证任务
|
||||||
|
│ ├── 验证成功 → RecordBundleFile
|
||||||
|
│ └── 验证失败 → DeleteFiles
|
||||||
|
│
|
||||||
|
└── 启动新的验证任务
|
||||||
|
└── ThreadPool.QueueUserWorkItem(VerifyInThread)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 验证级别
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
private EFileVerifyResult VerifyingCacheFile(VerifyFileElement element, EFileVerifyLevel verifyLevel)
|
||||||
|
{
|
||||||
|
if (verifyLevel == EFileVerifyLevel.Low)
|
||||||
|
{
|
||||||
|
// Low:仅检查文件存在
|
||||||
|
if (File.Exists(element.InfoFilePath) == false)
|
||||||
|
return EFileVerifyResult.InfoFileNotExisted;
|
||||||
|
if (File.Exists(element.DataFilePath) == false)
|
||||||
|
return EFileVerifyResult.DataFileNotExisted;
|
||||||
|
return EFileVerifyResult.Succeed;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Middle/High:检查文件存在 + CRC/Size 验证
|
||||||
|
_fileSystem.ReadBundleInfoFile(element.InfoFilePath, out element.DataFileCRC, out element.DataFileSize);
|
||||||
|
return FileVerifyHelper.FileVerify(element.DataFilePath, element.DataFileSize, element.DataFileCRC, verifyLevel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### VerifyTempFileOperation
|
||||||
|
|
||||||
|
下载文件验证,在线程池中执行。
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
private void VerifyInThread(object obj)
|
||||||
|
{
|
||||||
|
TempFileElement element = (TempFileElement)obj;
|
||||||
|
int result = (int)FileVerifyHelper.FileVerify(
|
||||||
|
element.TempFilePath,
|
||||||
|
element.TempFileSize,
|
||||||
|
element.TempFileCRC,
|
||||||
|
EFileVerifyLevel.High // 始终使用高级验证
|
||||||
|
);
|
||||||
|
element.Result = result; // 线程安全的结果设置
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 覆盖安装检测
|
||||||
|
|
||||||
|
### ApplicationFootPrint
|
||||||
|
|
||||||
|
应用版本足迹,用于检测 App 覆盖安装。
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public static bool IsDirty(DefaultCacheFileSystem fileSystem)
|
||||||
|
{
|
||||||
|
string filePath = fileSystem.GetSandboxAppFootPrintFilePath();
|
||||||
|
if (File.Exists(filePath))
|
||||||
|
{
|
||||||
|
string footPrint = FileUtility.ReadAllText(filePath);
|
||||||
|
return IsValidVersion(footPrint) == false; // 版本不同
|
||||||
|
}
|
||||||
|
return true; // 文件不存在
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### EOverwriteInstallClearMode
|
||||||
|
|
||||||
|
覆盖安装时的缓存清理模式。
|
||||||
|
|
||||||
|
| 枚举值 | 说明 |
|
||||||
|
|--------|------|
|
||||||
|
| `NeverClear` | 不清理任何缓存 |
|
||||||
|
| `ClearAllManifestFiles` | 清理所有清单文件(默认) |
|
||||||
|
| `ClearAllBundleAndManifestFiles` | 清理所有资源包和清单文件 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 缓存清理操作
|
||||||
|
|
||||||
|
### 清理模式对照表
|
||||||
|
|
||||||
|
| 清理模式 | 操作类 | 说明 |
|
||||||
|
|----------|--------|------|
|
||||||
|
| `ClearAllBundleFiles` | `ClearAllCacheBundleFilesOperation` | 清理所有缓存资源包 |
|
||||||
|
| `ClearUnusedBundleFiles` | `ClearUnusedCacheBundleFilesOperation` | 清理不在清单中的资源包 |
|
||||||
|
| `ClearBundleFilesByTags` | `ClearCacheBundleFilesByTagsOperaiton` | 按标签清理 |
|
||||||
|
| `ClearBundleFilesByLocations` | `ClearCacheBundleFilesByLocationsOperaiton` | 按资源路径清理 |
|
||||||
|
| `ClearAllManifestFiles` | `ClearAllCacheManifestFilesOperation` | 清理所有清单文件 |
|
||||||
|
| `ClearUnusedManifestFiles` | `ClearUnusedCacheManifestFilesOperation` | 清理未使用的清单文件 |
|
||||||
|
|
||||||
|
### 时间切片清理
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
for (int i = _allBundleGUIDs.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
string bundleGUID = _allBundleGUIDs[i];
|
||||||
|
_fileSystem.DeleteCacheBundleFile(bundleGUID);
|
||||||
|
_allBundleGUIDs.RemoveAt(i);
|
||||||
|
|
||||||
|
// 检查操作系统是否繁忙,避免阻塞主线程
|
||||||
|
if (OperationSystem.IsBusy)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 元素类说明
|
||||||
|
|
||||||
|
### RecordFileElement
|
||||||
|
|
||||||
|
缓存文件记录元素,存储已验证的缓存文件信息。
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
internal class RecordFileElement
|
||||||
|
{
|
||||||
|
public readonly string InfoFilePath; // 信息文件路径
|
||||||
|
public readonly string DataFilePath; // 数据文件路径
|
||||||
|
public readonly uint DataFileCRC; // 数据文件 CRC
|
||||||
|
public readonly long DataFileSize; // 数据文件大小
|
||||||
|
|
||||||
|
public bool DeleteFolder(); // 删除整个文件夹
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### TempFileElement
|
||||||
|
|
||||||
|
临时文件元素,用于下载文件验证。
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
internal class TempFileElement
|
||||||
|
{
|
||||||
|
public readonly string TempFilePath; // 临时文件路径
|
||||||
|
public readonly uint TempFileCRC; // 预期 CRC
|
||||||
|
public readonly long TempFileSize; // 预期文件大小
|
||||||
|
|
||||||
|
private int _result = 0;
|
||||||
|
public int Result // 线程安全的验证结果
|
||||||
|
{
|
||||||
|
get => Interlocked.CompareExchange(ref _result, 0, 0);
|
||||||
|
set => Interlocked.Exchange(ref _result, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### VerifyFileElement
|
||||||
|
|
||||||
|
验证文件元素,用于缓存文件批量验证。
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
internal class VerifyFileElement
|
||||||
|
{
|
||||||
|
public readonly string PackageName; // 包裹名称
|
||||||
|
public readonly string BundleGUID; // 资源包 GUID
|
||||||
|
public readonly string FileRootPath; // 文件根目录
|
||||||
|
public readonly string DataFilePath; // 数据文件路径
|
||||||
|
public readonly string InfoFilePath; // 信息文件路径
|
||||||
|
|
||||||
|
public uint DataFileCRC; // 数据文件 CRC
|
||||||
|
public long DataFileSize; // 数据文件大小
|
||||||
|
|
||||||
|
private int _result = 0;
|
||||||
|
public int Result // 线程安全的验证结果
|
||||||
|
{
|
||||||
|
get => Interlocked.CompareExchange(ref _result, 0, 0);
|
||||||
|
set => Interlocked.Exchange(ref _result, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteFiles(); // 删除所有相关文件
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 使用示例
|
||||||
|
|
||||||
|
### 基础配置
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 创建远程服务接口
|
||||||
|
class GameRemoteServices : IRemoteServices
|
||||||
|
{
|
||||||
|
public string GetRemoteMainURL(string fileName)
|
||||||
|
{
|
||||||
|
return $"https://cdn.example.com/bundles/{fileName}";
|
||||||
|
}
|
||||||
|
public string GetRemoteFallbackURL(string fileName)
|
||||||
|
{
|
||||||
|
return $"https://cdn-backup.example.com/bundles/{fileName}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建缓存文件系统参数
|
||||||
|
var cacheParams = FileSystemParameters.CreateDefaultCacheFileSystemParameters(
|
||||||
|
remoteServices: new GameRemoteServices()
|
||||||
|
);
|
||||||
|
|
||||||
|
// 初始化包裹
|
||||||
|
var initParams = new HostPlayModeParameters();
|
||||||
|
initParams.BuildinFileSystemParameters = buildinParams;
|
||||||
|
initParams.CacheFileSystemParameters = cacheParams;
|
||||||
|
var initOp = package.InitializeAsync(initParams);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 配置下载参数
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var cacheParams = FileSystemParameters.CreateDefaultCacheFileSystemParameters(
|
||||||
|
remoteServices: new GameRemoteServices()
|
||||||
|
);
|
||||||
|
|
||||||
|
// 设置下载并发数
|
||||||
|
cacheParams.AddParameter(FileSystemParametersDefine.DOWNLOAD_MAX_CONCURRENCY, 8);
|
||||||
|
|
||||||
|
// 设置每帧最大请求数
|
||||||
|
cacheParams.AddParameter(FileSystemParametersDefine.DOWNLOAD_MAX_REQUEST_PER_FRAME, 3);
|
||||||
|
|
||||||
|
// 设置下载看门狗时间(秒)
|
||||||
|
cacheParams.AddParameter(FileSystemParametersDefine.DOWNLOAD_WATCH_DOG_TIME, 30);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 配置断点续传
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var cacheParams = FileSystemParameters.CreateDefaultCacheFileSystemParameters(
|
||||||
|
remoteServices: new GameRemoteServices()
|
||||||
|
);
|
||||||
|
|
||||||
|
// 启用断点续传的最小文件大小(1MB)
|
||||||
|
cacheParams.AddParameter(FileSystemParametersDefine.RESUME_DOWNLOAD_MINMUM_SIZE, 1024 * 1024);
|
||||||
|
|
||||||
|
// 断点续传关注的HTTP错误码(这些错误码时删除临时文件重新下载)
|
||||||
|
var responseCodes = new List<long> { 416 }; // Range Not Satisfiable
|
||||||
|
cacheParams.AddParameter(FileSystemParametersDefine.RESUME_DOWNLOAD_RESPONSE_CODES, responseCodes);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 配置文件验证
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var cacheParams = FileSystemParameters.CreateDefaultCacheFileSystemParameters(
|
||||||
|
remoteServices: new GameRemoteServices()
|
||||||
|
);
|
||||||
|
|
||||||
|
// 设置验证级别
|
||||||
|
cacheParams.AddParameter(FileSystemParametersDefine.FILE_VERIFY_LEVEL, EFileVerifyLevel.High);
|
||||||
|
|
||||||
|
// 设置验证并发数
|
||||||
|
cacheParams.AddParameter(FileSystemParametersDefine.FILE_VERIFY_MAX_CONCURRENCY, 64);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 配置加密支持
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 自定义解密服务
|
||||||
|
class GameDecryptionServices : IDecryptionServices
|
||||||
|
{
|
||||||
|
public DecryptResult LoadAssetBundle(DecryptFileInfo fileInfo)
|
||||||
|
{
|
||||||
|
// 实现解密逻辑
|
||||||
|
byte[] data = DecryptFile(fileInfo.FileLoadPath);
|
||||||
|
AssetBundle bundle = AssetBundle.LoadFromMemory(data);
|
||||||
|
return new DecryptResult { Result = bundle };
|
||||||
|
}
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
var cacheParams = FileSystemParameters.CreateDefaultCacheFileSystemParameters(
|
||||||
|
remoteServices: new GameRemoteServices()
|
||||||
|
);
|
||||||
|
|
||||||
|
// 设置解密服务
|
||||||
|
cacheParams.AddParameter(FileSystemParametersDefine.DECRYPTION_SERVICES, new GameDecryptionServices());
|
||||||
|
```
|
||||||
|
|
||||||
|
### 配置覆盖安装行为
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var cacheParams = FileSystemParameters.CreateDefaultCacheFileSystemParameters(
|
||||||
|
remoteServices: new GameRemoteServices()
|
||||||
|
);
|
||||||
|
|
||||||
|
// 覆盖安装时清理所有资源包和清单
|
||||||
|
cacheParams.AddParameter(
|
||||||
|
FileSystemParametersDefine.INSTALL_CLEAR_MODE,
|
||||||
|
EOverwriteInstallClearMode.ClearAllBundleAndManifestFiles
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 清理缓存
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 清理所有缓存
|
||||||
|
var clearOp = package.ClearCacheFilesAsync(EFileClearMode.ClearAllBundleFiles);
|
||||||
|
await clearOp.ToTask();
|
||||||
|
|
||||||
|
// 清理未使用的缓存
|
||||||
|
var clearOp = package.ClearCacheFilesAsync(EFileClearMode.ClearUnusedBundleFiles);
|
||||||
|
await clearOp.ToTask();
|
||||||
|
|
||||||
|
// 按标签清理
|
||||||
|
var clearOp = package.ClearCacheFilesAsync(EFileClearMode.ClearBundleFilesByTags, "dlc1");
|
||||||
|
await clearOp.ToTask();
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 参数常量
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 远程服务
|
||||||
|
FileSystemParametersDefine.REMOTE_SERVICES // IRemoteServices: 远程服务接口
|
||||||
|
|
||||||
|
// 覆盖安装
|
||||||
|
FileSystemParametersDefine.INSTALL_CLEAR_MODE // EOverwriteInstallClearMode: 清理模式
|
||||||
|
|
||||||
|
// 文件验证
|
||||||
|
FileSystemParametersDefine.FILE_VERIFY_LEVEL // EFileVerifyLevel: 验证级别
|
||||||
|
FileSystemParametersDefine.FILE_VERIFY_MAX_CONCURRENCY // int: 验证并发数
|
||||||
|
|
||||||
|
// 文件格式
|
||||||
|
FileSystemParametersDefine.APPEND_FILE_EXTENSION // bool: 追加文件扩展名
|
||||||
|
|
||||||
|
// 下载控制
|
||||||
|
FileSystemParametersDefine.DISABLE_ONDEMAND_DOWNLOAD // bool: 禁用边玩边下
|
||||||
|
FileSystemParametersDefine.DOWNLOAD_MAX_CONCURRENCY // int: 下载并发数
|
||||||
|
FileSystemParametersDefine.DOWNLOAD_MAX_REQUEST_PER_FRAME // int: 每帧请求数
|
||||||
|
FileSystemParametersDefine.DOWNLOAD_WATCH_DOG_TIME // int: 看门狗时间
|
||||||
|
|
||||||
|
// 断点续传
|
||||||
|
FileSystemParametersDefine.RESUME_DOWNLOAD_MINMUM_SIZE // long: 最小文件大小
|
||||||
|
FileSystemParametersDefine.RESUME_DOWNLOAD_RESPONSE_CODES // List<long>: 关注错误码
|
||||||
|
|
||||||
|
// 服务接口
|
||||||
|
FileSystemParametersDefine.DECRYPTION_SERVICES // IDecryptionServices: 解密服务
|
||||||
|
FileSystemParametersDefine.MANIFEST_SERVICES // IManifestRestoreServices: 清单服务
|
||||||
|
FileSystemParametersDefine.COPY_LOCAL_FILE_SERVICES // ICopyLocalFileServices: 本地拷贝服务
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 类继承关系
|
||||||
|
|
||||||
|
```
|
||||||
|
IFileSystem
|
||||||
|
└── DefaultCacheFileSystem
|
||||||
|
|
||||||
|
FSInitializeFileSystemOperation
|
||||||
|
└── DCFSInitializeOperation
|
||||||
|
|
||||||
|
FSRequestPackageVersionOperation
|
||||||
|
└── DCFSRequestPackageVersionOperation
|
||||||
|
|
||||||
|
FSLoadPackageManifestOperation
|
||||||
|
└── DCFSLoadPackageManifestOperation
|
||||||
|
|
||||||
|
FSLoadBundleOperation
|
||||||
|
├── DCFSLoadAssetBundleOperation
|
||||||
|
└── DCFSLoadRawBundleOperation
|
||||||
|
|
||||||
|
FSDownloadFileOperation
|
||||||
|
└── DownloadPackageBundleOperation
|
||||||
|
|
||||||
|
FSClearCacheFilesOperation
|
||||||
|
├── ClearAllCacheBundleFilesOperation
|
||||||
|
├── ClearUnusedCacheBundleFilesOperation
|
||||||
|
├── ClearCacheBundleFilesByTagsOperaiton
|
||||||
|
├── ClearCacheBundleFilesByLocationsOperaiton
|
||||||
|
├── ClearAllCacheManifestFilesOperation
|
||||||
|
└── ClearUnusedCacheManifestFilesOperation
|
||||||
|
|
||||||
|
AsyncOperationBase
|
||||||
|
├── DownloadSchedulerOperation
|
||||||
|
├── DownloadAndCacheFileOperation (abstract)
|
||||||
|
│ ├── DownloadAndCacheRemoteFileOperation
|
||||||
|
│ └── DownloadAndCacheLocalFileOperation
|
||||||
|
├── SearchCacheFilesOperation
|
||||||
|
├── VerifyCacheFilesOperation
|
||||||
|
├── VerifyTempFileOperation
|
||||||
|
├── DownloadPackageHashOperation
|
||||||
|
├── DownloadPackageManifestOperation
|
||||||
|
├── LoadCachePackageHashOperation
|
||||||
|
├── LoadCachePackageManifestOperation
|
||||||
|
└── RequestRemotePackageVersionOperation
|
||||||
|
|
||||||
|
BundleResult
|
||||||
|
├── AssetBundleResult ← AssetBundle 资源
|
||||||
|
└── RawBundleResult ← 原生文件资源
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **远程服务必需**:使用缓存文件系统必须配置 `IRemoteServices` 接口
|
||||||
|
2. **保底加载**:缓存文件系统的 `Belong()` 始终返回 true,作为资源加载的保底方案
|
||||||
|
3. **线程安全**:文件验证和下载使用后台线程,但核心逻辑仍在主线程执行
|
||||||
|
4. **并发限制**:合理设置下载和验证的并发数,避免系统过载
|
||||||
|
5. **移动平台**:Android 平台存在极小概率的 AssetBundle 加载失败,系统会自动尝试 LoadFromMemory 作为备选方案
|
||||||
|
6. **断点续传**:启用断点续传时,需要合理设置 `ResumeDownloadMinimumSize` 和 `ResumeDownloadResponseCodes`
|
||||||
|
7. **覆盖安装**:App 版本更新时会自动检测并根据配置清理缓存,避免旧缓存导致问题
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 8292f707fbf60854e852e1a75824d892
|
||||||
|
TextScriptImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -11,6 +11,11 @@ namespace YooAsset
|
|||||||
protected readonly Dictionary<string, string> _records = new Dictionary<string, string>(10000);
|
protected readonly Dictionary<string, string> _records = new Dictionary<string, string>(10000);
|
||||||
protected string _packageRoot;
|
protected string _packageRoot;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 下载后台接口
|
||||||
|
/// </summary>
|
||||||
|
public IDownloadBackend DownloadBackend { private set; get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 包裹名称
|
/// 包裹名称
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -39,6 +44,11 @@ namespace YooAsset
|
|||||||
}
|
}
|
||||||
|
|
||||||
#region 自定义参数
|
#region 自定义参数
|
||||||
|
/// <summary>
|
||||||
|
/// 自定义参数:UnityWebRequest 创建委托
|
||||||
|
/// </summary>
|
||||||
|
public UnityWebRequestCreator WebRequestCreator { private set; get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 模拟WebGL平台模式
|
/// 模拟WebGL平台模式
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -112,7 +122,15 @@ namespace YooAsset
|
|||||||
|
|
||||||
public virtual void SetParameter(string name, object value)
|
public virtual void SetParameter(string name, object value)
|
||||||
{
|
{
|
||||||
if (name == FileSystemParametersDefine.VIRTUAL_WEBGL_MODE)
|
if (name == FileSystemParametersDefine.DOWNLOAD_BACKEND)
|
||||||
|
{
|
||||||
|
DownloadBackend = (IDownloadBackend)value;
|
||||||
|
}
|
||||||
|
else if (name == FileSystemParametersDefine.UNITY_WEB_REQUEST_CREATOR)
|
||||||
|
{
|
||||||
|
WebRequestCreator = (UnityWebRequestCreator)value;
|
||||||
|
}
|
||||||
|
else if (name == FileSystemParametersDefine.VIRTUAL_WEBGL_MODE)
|
||||||
{
|
{
|
||||||
VirtualWebGLMode = Convert.ToBoolean(value);
|
VirtualWebGLMode = Convert.ToBoolean(value);
|
||||||
}
|
}
|
||||||
@@ -142,12 +160,21 @@ namespace YooAsset
|
|||||||
PackageName = packageName;
|
PackageName = packageName;
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(packageRoot))
|
if (string.IsNullOrEmpty(packageRoot))
|
||||||
throw new Exception($"{nameof(DefaultEditorFileSystem)} root directory is null or empty !");
|
throw new YooFileSystemException($"{nameof(DefaultEditorFileSystem)} package root is null or empty !");
|
||||||
|
|
||||||
_packageRoot = packageRoot;
|
_packageRoot = packageRoot;
|
||||||
|
|
||||||
|
// 创建默认的下载后台接口
|
||||||
|
if (DownloadBackend == null)
|
||||||
|
DownloadBackend = new UnityWebRequestBackend(WebRequestCreator);
|
||||||
}
|
}
|
||||||
public virtual void OnDestroy()
|
public virtual void OnDestroy()
|
||||||
{
|
{
|
||||||
|
if (DownloadBackend != null)
|
||||||
|
{
|
||||||
|
DownloadBackend.Dispose();
|
||||||
|
DownloadBackend = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual bool Belong(PackageBundle bundle)
|
public virtual bool Belong(PackageBundle bundle)
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ namespace YooAsset
|
|||||||
// 下载参数
|
// 下载参数
|
||||||
protected readonly DefaultEditorFileSystem _fileSystem;
|
protected readonly DefaultEditorFileSystem _fileSystem;
|
||||||
protected readonly DownloadFileOptions _options;
|
protected readonly DownloadFileOptions _options;
|
||||||
protected UnityVirtualBundleRequestOperation _unityDownloadFileOp;
|
protected IDownloadFileRequest _downloadFileOp;
|
||||||
|
|
||||||
protected int _requestCount = 0;
|
protected int _requestCount = 0;
|
||||||
protected float _tryAgainTimer = 0;
|
protected float _tryAgainTimer = 0;
|
||||||
@@ -68,29 +68,22 @@ namespace YooAsset
|
|||||||
|
|
||||||
string url = GetRequestURL();
|
string url = GetRequestURL();
|
||||||
int speed = _fileSystem.VirtualDownloadSpeed;
|
int speed = _fileSystem.VirtualDownloadSpeed;
|
||||||
_unityDownloadFileOp = new UnityVirtualBundleRequestOperation(Bundle, speed, url);
|
var args = new DownloadSimulateRequestArgs(url, Bundle.FileSize, speed);
|
||||||
_unityDownloadFileOp.StartOperation();
|
_downloadFileOp = _fileSystem.DownloadBackend.CreateSimulateRequest(args);
|
||||||
|
_downloadFileOp.SendRequest();
|
||||||
_steps = ESteps.CheckRequest;
|
_steps = ESteps.CheckRequest;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检测下载结果
|
// 检测下载结果
|
||||||
if (_steps == ESteps.CheckRequest)
|
if (_steps == ESteps.CheckRequest)
|
||||||
{
|
{
|
||||||
if (IsWaitForAsyncComplete)
|
Progress = _downloadFileOp.DownloadProgress;
|
||||||
_unityDownloadFileOp.WaitForAsyncComplete();
|
DownloadedBytes = _downloadFileOp.DownloadedBytes;
|
||||||
|
DownloadProgress = _downloadFileOp.DownloadProgress;
|
||||||
// 因为并发数量限制,下载器可能被挂起!
|
if (_downloadFileOp.IsDone == false)
|
||||||
if (_unityDownloadFileOp.Status == EOperationStatus.None)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_unityDownloadFileOp.UpdateOperation();
|
if (_downloadFileOp.Status == EDownloadRequestStatus.Succeed)
|
||||||
Progress = _unityDownloadFileOp.Progress;
|
|
||||||
DownloadedBytes = _unityDownloadFileOp.DownloadedBytes;
|
|
||||||
DownloadProgress = _unityDownloadFileOp.DownloadProgress;
|
|
||||||
if (_unityDownloadFileOp.IsDone == false)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (_unityDownloadFileOp.Status == EOperationStatus.Succeed)
|
|
||||||
{
|
{
|
||||||
_fileSystem.RecordDownloadFile(Bundle);
|
_fileSystem.RecordDownloadFile(Bundle);
|
||||||
_steps = ESteps.Done;
|
_steps = ESteps.Done;
|
||||||
@@ -101,13 +94,13 @@ namespace YooAsset
|
|||||||
if (IsWaitForAsyncComplete == false && _failedTryAgain > 0)
|
if (IsWaitForAsyncComplete == false && _failedTryAgain > 0)
|
||||||
{
|
{
|
||||||
_steps = ESteps.TryAgain;
|
_steps = ESteps.TryAgain;
|
||||||
YooLogger.Warning($"Failed download : {_unityDownloadFileOp.URL} Try again !");
|
YooLogger.Warning($"Failed download : {_downloadFileOp.URL} Try again !");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_steps = ESteps.Done;
|
_steps = ESteps.Done;
|
||||||
Status = EOperationStatus.Failed;
|
Status = EOperationStatus.Failed;
|
||||||
Error = _unityDownloadFileOp.Error;
|
Error = _downloadFileOp.Error;
|
||||||
YooLogger.Error(Error);
|
YooLogger.Error(Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -130,13 +123,12 @@ namespace YooAsset
|
|||||||
}
|
}
|
||||||
internal override void InternalWaitForAsyncComplete()
|
internal override void InternalWaitForAsyncComplete()
|
||||||
{
|
{
|
||||||
while (true)
|
if (_steps != ESteps.Done)
|
||||||
{
|
|
||||||
if (ExecuteWhileDone())
|
|
||||||
{
|
{
|
||||||
_steps = ESteps.Done;
|
_steps = ESteps.Done;
|
||||||
break;
|
Status = EOperationStatus.Failed;
|
||||||
}
|
Error = $"Try load bundle {Bundle.BundleName} from remote !";
|
||||||
|
YooLogger.Error(Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,424 @@
|
|||||||
|
# DefaultEditorFileSystem 编辑器模拟文件系统
|
||||||
|
|
||||||
|
## 模块概述
|
||||||
|
|
||||||
|
DefaultEditorFileSystem 是 YooAsset 的**编辑器模拟文件系统**,专为 Unity 编辑器开发环境设计。该文件系统无需构建实际的 AssetBundle 文件,直接使用 Unity 的 AssetDatabase API 加载资源,实现快速迭代开发。
|
||||||
|
|
||||||
|
### 核心特性
|
||||||
|
|
||||||
|
- **无需构建资源包**:直接使用 AssetDatabase 加载资源
|
||||||
|
- **模拟下载流程**:支持模拟网络下载行为(用于 UI 调试)
|
||||||
|
- **模拟异步延迟**:可配置异步加载的模拟帧数
|
||||||
|
- **WebGL 模式模拟**:支持模拟 WebGL 平台行为
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 设计目标
|
||||||
|
|
||||||
|
| 目标 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| **快速迭代** | 无需构建 AssetBundle,修改资源后立即生效 |
|
||||||
|
| **行为模拟** | 模拟真实环境的下载和加载行为 |
|
||||||
|
| **调试友好** | 支持 UI 进度条等功能的调试 |
|
||||||
|
| **零配置** | 开箱即用,最小化配置需求 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 文件结构
|
||||||
|
|
||||||
|
```
|
||||||
|
DefaultEditorFileSystem/
|
||||||
|
├── DefaultEditorFileSystem.cs # 文件系统主类
|
||||||
|
├── DefaultEditorFileSystemDefine.cs # 常量定义(预留)
|
||||||
|
└── Operation/ # 操作类
|
||||||
|
├── DEFSInitializeOperation.cs # 初始化操作
|
||||||
|
├── DEFSRequestPackageVersionOperation.cs # 请求版本操作
|
||||||
|
├── DEFSLoadPackageManifestOperation.cs # 加载清单操作
|
||||||
|
├── DEFSLoadBundleOperation.cs # 加载资源包操作
|
||||||
|
└── internal/ # 内部操作类
|
||||||
|
├── DownloadVirutalBundleOperation.cs # 虚拟下载操作
|
||||||
|
├── LoadEditorPackageVersionOperation.cs # 加载版本文件操作
|
||||||
|
├── LoadEditorPackageHashOperation.cs # 加载哈希文件操作
|
||||||
|
└── LoadEditorPackageManifestOperation.cs # 加载清单文件操作
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 核心类说明
|
||||||
|
|
||||||
|
### DefaultEditorFileSystem
|
||||||
|
|
||||||
|
编辑器模拟文件系统的主类,实现 `IFileSystem` 接口。
|
||||||
|
|
||||||
|
#### 基本属性
|
||||||
|
|
||||||
|
| 属性 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `PackageName` | `string` | 包裹名称 |
|
||||||
|
| `FileRoot` | `string` | 文件根目录(清单文件所在目录) |
|
||||||
|
| `FileCount` | `int` | 文件数量(始终返回 0) |
|
||||||
|
| `DownloadBackend` | `IDownloadBackend` | 下载后台接口 |
|
||||||
|
|
||||||
|
#### 自定义参数
|
||||||
|
|
||||||
|
| 参数 | 类型 | 默认值 | 说明 |
|
||||||
|
|------|------|--------|------|
|
||||||
|
| `VirtualWebGLMode` | `bool` | `false` | 模拟 WebGL 平台模式 |
|
||||||
|
| `VirtualDownloadMode` | `bool` | `false` | 模拟虚拟下载模式 |
|
||||||
|
| `VirtualDownloadSpeed` | `int` | `1024` | 模拟下载速度(字节/秒) |
|
||||||
|
| `AsyncSimulateMinFrame` | `int` | `1` | 异步加载最小模拟帧数 |
|
||||||
|
| `AsyncSimulateMaxFrame` | `int` | `1` | 异步加载最大模拟帧数 |
|
||||||
|
|
||||||
|
#### 核心方法
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 生命周期
|
||||||
|
void OnCreate(string packageName, string packageRoot);
|
||||||
|
void OnDestroy();
|
||||||
|
void SetParameter(string name, object value);
|
||||||
|
|
||||||
|
// 异步操作
|
||||||
|
FSInitializeFileSystemOperation InitializeFileSystemAsync();
|
||||||
|
FSRequestPackageVersionOperation RequestPackageVersionAsync(bool appendTimeTicks, int timeout);
|
||||||
|
FSLoadPackageManifestOperation LoadPackageManifestAsync(string packageVersion, int timeout);
|
||||||
|
FSLoadBundleOperation LoadBundleFile(PackageBundle bundle);
|
||||||
|
FSDownloadFileOperation DownloadFileAsync(PackageBundle bundle, DownloadFileOptions options);
|
||||||
|
FSClearCacheFilesOperation ClearCacheFilesAsync(PackageManifest manifest, ClearCacheFilesOptions options);
|
||||||
|
|
||||||
|
// 文件查询
|
||||||
|
bool Belong(PackageBundle bundle); // 始终返回 true
|
||||||
|
bool Exists(PackageBundle bundle); // VirtualDownloadMode 时检查记录
|
||||||
|
bool NeedDownload(PackageBundle bundle);// VirtualDownloadMode 时返回未记录的文件
|
||||||
|
bool NeedUnpack(PackageBundle bundle); // 始终返回 false
|
||||||
|
bool NeedImport(PackageBundle bundle); // 始终返回 false
|
||||||
|
|
||||||
|
// 文件访问
|
||||||
|
string GetBundleFilePath(PackageBundle bundle); // 返回资源路径
|
||||||
|
byte[] ReadBundleFileData(PackageBundle bundle); // 读取文件二进制
|
||||||
|
string ReadBundleFileText(PackageBundle bundle); // 读取文件文本
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 操作类说明
|
||||||
|
|
||||||
|
### DEFSInitializeOperation
|
||||||
|
|
||||||
|
初始化操作,立即完成(无需任何初始化工作)。
|
||||||
|
|
||||||
|
```
|
||||||
|
状态流程:InternalStart() → Status = Succeed
|
||||||
|
```
|
||||||
|
|
||||||
|
### DEFSRequestPackageVersionOperation
|
||||||
|
|
||||||
|
请求包裹版本操作,从本地版本文件读取版本号。
|
||||||
|
|
||||||
|
```
|
||||||
|
状态流程:
|
||||||
|
LoadPackageVersion
|
||||||
|
└── LoadEditorPackageVersionOperation
|
||||||
|
└── 读取 {PackageName}_Version.txt
|
||||||
|
├── 成功 → PackageVersion = 文件内容
|
||||||
|
└── 失败 → Error
|
||||||
|
```
|
||||||
|
|
||||||
|
### DEFSLoadPackageManifestOperation
|
||||||
|
|
||||||
|
加载资源清单操作,从本地清单文件加载并解析清单。
|
||||||
|
|
||||||
|
```
|
||||||
|
状态流程:
|
||||||
|
LoadEditorPackageHash
|
||||||
|
└── LoadEditorPackageHashOperation
|
||||||
|
└── 读取 {PackageName}_{Version}.hash
|
||||||
|
├── 成功 → PackageHash
|
||||||
|
└── 失败 → Error
|
||||||
|
↓
|
||||||
|
LoadEditorPackageManifest
|
||||||
|
└── LoadEditorPackageManifestOperation
|
||||||
|
├── 读取 {PackageName}_{Version}.bytes
|
||||||
|
├── 验证哈希值
|
||||||
|
└── 反序列化清单
|
||||||
|
├── 成功 → Manifest
|
||||||
|
└── 失败 → Error
|
||||||
|
```
|
||||||
|
|
||||||
|
### DEFSLoadBundleOperation
|
||||||
|
|
||||||
|
加载资源包操作,支持虚拟下载模式和异步模拟延迟。
|
||||||
|
|
||||||
|
```
|
||||||
|
状态流程:
|
||||||
|
CheckExist
|
||||||
|
├── 文件存在 → LoadAssetBundle
|
||||||
|
└── 文件不存在 → DownloadFile
|
||||||
|
↓
|
||||||
|
DownloadFile
|
||||||
|
└── DownloadVirtualBundleOperation
|
||||||
|
├── 模拟下载进度
|
||||||
|
└── 记录下载完成
|
||||||
|
↓
|
||||||
|
LoadAssetBundle
|
||||||
|
└── 等待模拟帧数
|
||||||
|
↓
|
||||||
|
CheckResult
|
||||||
|
└── 创建 VirtualBundleResult → Status = Succeed
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 状态机枚举
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
private enum ESteps
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
CheckExist, // 检查文件是否存在
|
||||||
|
DownloadFile, // 下载文件(虚拟下载)
|
||||||
|
AbortDownload, // 中断下载
|
||||||
|
LoadAssetBundle, // 加载资源包(模拟延迟)
|
||||||
|
CheckResult, // 检查结果
|
||||||
|
Done // 完成
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### DownloadVirtualBundleOperation
|
||||||
|
|
||||||
|
虚拟下载操作,模拟网络下载行为。
|
||||||
|
|
||||||
|
**特性:**
|
||||||
|
- 使用 `VirtualFileDownloader` 模拟下载进度
|
||||||
|
- 支持失败重试机制
|
||||||
|
- 下载完成后记录到 `_records` 字典
|
||||||
|
|
||||||
|
```
|
||||||
|
状态流程:
|
||||||
|
CheckExists
|
||||||
|
├── 文件已记录 → Status = Succeed
|
||||||
|
└── 文件未记录 → CreateRequest
|
||||||
|
↓
|
||||||
|
CreateRequest
|
||||||
|
└── DownloadSimulateRequestArgs
|
||||||
|
├── URL = BundleName
|
||||||
|
├── FileSize = Bundle.FileSize
|
||||||
|
└── DownloadSpeed = VirtualDownloadSpeed
|
||||||
|
↓
|
||||||
|
CheckRequest
|
||||||
|
├── 下载成功 → RecordDownloadFile() → Status = Succeed
|
||||||
|
└── 下载失败 → TryAgain 或 Status = Failed
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 内部操作类
|
||||||
|
|
||||||
|
### LoadEditorPackageVersionOperation
|
||||||
|
|
||||||
|
从本地文件加载包裹版本号。
|
||||||
|
|
||||||
|
| 属性 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `PackageVersion` | 读取到的版本号字符串 |
|
||||||
|
|
||||||
|
### LoadEditorPackageHashOperation
|
||||||
|
|
||||||
|
从本地文件加载包裹哈希值。
|
||||||
|
|
||||||
|
| 属性 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `PackageHash` | 读取到的哈希值字符串 |
|
||||||
|
|
||||||
|
### LoadEditorPackageManifestOperation
|
||||||
|
|
||||||
|
加载并反序列化资源清单。
|
||||||
|
|
||||||
|
| 属性 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `Manifest` | 反序列化后的清单对象 |
|
||||||
|
|
||||||
|
**处理流程:**
|
||||||
|
1. 读取清单二进制文件
|
||||||
|
2. 使用哈希值验证文件完整性
|
||||||
|
3. 反序列化为 `PackageManifest` 对象
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 工作原理
|
||||||
|
|
||||||
|
### 资源加载机制
|
||||||
|
|
||||||
|
```
|
||||||
|
用户请求加载资源
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
DefaultEditorFileSystem.LoadBundleFile()
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
DEFSLoadBundleOperation
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
创建 VirtualBundleResult
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
VirtualBundleResult.LoadAssetAsync()
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
VirtualBundleLoadAssetOperation
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
AssetDatabase.LoadAssetAtPath() ← Unity 编辑器 API
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
返回资源对象
|
||||||
|
```
|
||||||
|
|
||||||
|
### 虚拟下载模式
|
||||||
|
|
||||||
|
当 `VirtualDownloadMode = true` 时:
|
||||||
|
|
||||||
|
1. **首次加载**:资源被视为"未下载",需要执行虚拟下载
|
||||||
|
2. **虚拟下载**:使用 `VirtualFileDownloader` 模拟下载进度
|
||||||
|
3. **记录完成**:下载完成后将 BundleGUID 记录到 `_records` 字典
|
||||||
|
4. **后续加载**:检查 `_records` 字典,已记录的资源直接加载
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 记录下载完成的文件
|
||||||
|
protected readonly Dictionary<string, string> _records;
|
||||||
|
|
||||||
|
// 检查文件是否存在
|
||||||
|
public virtual bool Exists(PackageBundle bundle)
|
||||||
|
{
|
||||||
|
if (VirtualDownloadMode)
|
||||||
|
return _records.ContainsKey(bundle.BundleGUID);
|
||||||
|
else
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 异步模拟延迟
|
||||||
|
|
||||||
|
通过 `AsyncSimulateMinFrame` 和 `AsyncSimulateMaxFrame` 参数模拟异步加载延迟:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 获取随机模拟帧数
|
||||||
|
public int GetAsyncSimulateFrame()
|
||||||
|
{
|
||||||
|
return UnityEngine.Random.Range(AsyncSimulateMinFrame, AsyncSimulateMaxFrame + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在 DEFSLoadBundleOperation 中等待
|
||||||
|
if (_steps == ESteps.LoadAssetBundle)
|
||||||
|
{
|
||||||
|
if (_asyncSimulateFrame <= 0)
|
||||||
|
_steps = ESteps.CheckResult;
|
||||||
|
else
|
||||||
|
_asyncSimulateFrame--;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 使用示例
|
||||||
|
|
||||||
|
### 基础配置
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 创建编辑器文件系统参数
|
||||||
|
var editorParams = FileSystemParameters.CreateDefaultEditorFileSystemParameters(
|
||||||
|
packageRoot: "Assets/GameRes/Bundles/DefaultPackage"
|
||||||
|
);
|
||||||
|
|
||||||
|
// 初始化包裹
|
||||||
|
var initParams = new EditorSimulateModeParameters();
|
||||||
|
initParams.EditorFileSystemParameters = editorParams;
|
||||||
|
var initOp = package.InitializeAsync(initParams);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 启用虚拟下载模式
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var editorParams = FileSystemParameters.CreateDefaultEditorFileSystemParameters(
|
||||||
|
packageRoot: "Assets/GameRes/Bundles/DefaultPackage"
|
||||||
|
);
|
||||||
|
|
||||||
|
// 启用虚拟下载模式
|
||||||
|
editorParams.AddParameter(FileSystemParametersDefine.VIRTUAL_DOWNLOAD_MODE, true);
|
||||||
|
editorParams.AddParameter(FileSystemParametersDefine.VIRTUAL_DOWNLOAD_SPEED, 1024 * 100); // 100KB/s
|
||||||
|
```
|
||||||
|
|
||||||
|
### 配置异步模拟延迟
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var editorParams = FileSystemParameters.CreateDefaultEditorFileSystemParameters(
|
||||||
|
packageRoot: "Assets/GameRes/Bundles/DefaultPackage"
|
||||||
|
);
|
||||||
|
|
||||||
|
// 设置异步加载延迟 1-3 帧
|
||||||
|
editorParams.AddParameter(FileSystemParametersDefine.ASYNC_SIMULATE_MIN_FRAME, 1);
|
||||||
|
editorParams.AddParameter(FileSystemParametersDefine.ASYNC_SIMULATE_MAX_FRAME, 3);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 模拟 WebGL 模式
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var editorParams = FileSystemParameters.CreateDefaultEditorFileSystemParameters(
|
||||||
|
packageRoot: "Assets/GameRes/Bundles/DefaultPackage"
|
||||||
|
);
|
||||||
|
|
||||||
|
// 启用 WebGL 模拟模式
|
||||||
|
editorParams.AddParameter(FileSystemParametersDefine.VIRTUAL_WEBGL_MODE, true);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 参数常量
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 模拟模式参数
|
||||||
|
FileSystemParametersDefine.VIRTUAL_WEBGL_MODE // bool: 模拟 WebGL 模式
|
||||||
|
FileSystemParametersDefine.VIRTUAL_DOWNLOAD_MODE // bool: 模拟下载模式
|
||||||
|
FileSystemParametersDefine.VIRTUAL_DOWNLOAD_SPEED // int: 模拟下载速度(字节/秒)
|
||||||
|
FileSystemParametersDefine.ASYNC_SIMULATE_MIN_FRAME // int: 异步模拟最小帧数
|
||||||
|
FileSystemParametersDefine.ASYNC_SIMULATE_MAX_FRAME // int: 异步模拟最大帧数
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 类继承关系
|
||||||
|
|
||||||
|
```
|
||||||
|
IFileSystem
|
||||||
|
└── DefaultEditorFileSystem
|
||||||
|
|
||||||
|
FSInitializeFileSystemOperation
|
||||||
|
└── DEFSInitializeOperation
|
||||||
|
|
||||||
|
FSRequestPackageVersionOperation
|
||||||
|
└── DEFSRequestPackageVersionOperation
|
||||||
|
|
||||||
|
FSLoadPackageManifestOperation
|
||||||
|
└── DEFSLoadPackageManifestOperation
|
||||||
|
|
||||||
|
FSLoadBundleOperation
|
||||||
|
└── DEFSLoadBundleOperation
|
||||||
|
|
||||||
|
FSDownloadFileOperation
|
||||||
|
└── DownloadVirtualBundleOperation
|
||||||
|
|
||||||
|
AsyncOperationBase
|
||||||
|
├── LoadEditorPackageVersionOperation
|
||||||
|
├── LoadEditorPackageHashOperation
|
||||||
|
└── LoadEditorPackageManifestOperation
|
||||||
|
|
||||||
|
BundleResult
|
||||||
|
└── VirtualBundleResult ← 编辑器模式专用
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **仅限编辑器**:此文件系统仅在 Unity 编辑器中有效
|
||||||
|
2. **需要构建清单**:虽然不需要构建 AssetBundle,但需要构建资源清单文件
|
||||||
|
3. **VirtualBundle 类型**:只支持 `EBuildBundleType.VirtualBundle` 类型的资源包
|
||||||
|
4. **WebGL 模式限制**:`VirtualWebGLMode` 下不支持同步加载(`WaitForAsyncComplete`)
|
||||||
|
5. **性能差异**:编辑器模式下的加载性能与真机不同,仅供开发调试使用
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 10ca037f4f07977458b8e94e4ea0f32c
|
||||||
|
TextScriptImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,385 @@
|
|||||||
|
# DefaultUnpackFileSystem 解压文件系统
|
||||||
|
|
||||||
|
## 模块概述
|
||||||
|
|
||||||
|
DefaultUnpackFileSystem 是 YooAsset 的**解压文件系统**,专为处理 Android 和 OpenHarmony 平台的内置资源解压需求而设计。该文件系统继承自 `DefaultCacheFileSystem`,复用其完整的下载、验证、缓存功能,仅重定义存储目录结构。
|
||||||
|
|
||||||
|
### 核心特性
|
||||||
|
|
||||||
|
- **继承复用**:完全继承 DefaultCacheFileSystem 的所有功能
|
||||||
|
- **独立存储**:使用独立的目录存储解压后的资源
|
||||||
|
- **本地下载**:通过 WWW 路径从 StreamingAssets "下载"资源
|
||||||
|
- **平台适配**:解决 Android APK 内文件无法直接访问的问题
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 设计目标
|
||||||
|
|
||||||
|
| 目标 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| **平台兼容** | 解决 Android/OpenHarmony 平台 APK 内文件访问限制 |
|
||||||
|
| **代码复用** | 继承 DefaultCacheFileSystem,避免重复实现 |
|
||||||
|
| **资源隔离** | 解压资源与下载缓存分开存储,便于管理 |
|
||||||
|
| **透明集成** | 作为 DefaultBuildinFileSystem 的内部组件工作 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 文件结构
|
||||||
|
|
||||||
|
```
|
||||||
|
DefaultUnpackFileSystem/
|
||||||
|
├── DefaultUnpackFileSystem.cs # 解压文件系统主类
|
||||||
|
├── DefaultUnpackFileSystemDefine.cs # 常量定义
|
||||||
|
└── DefaultUnpackRemoteServices.cs # 本地资源服务接口
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 核心类说明
|
||||||
|
|
||||||
|
### DefaultUnpackFileSystem
|
||||||
|
|
||||||
|
解压文件系统主类,继承自 `DefaultCacheFileSystem`。
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
internal class DefaultUnpackFileSystem : DefaultCacheFileSystem
|
||||||
|
{
|
||||||
|
public override void OnCreate(string packageName, string rootDirectory)
|
||||||
|
{
|
||||||
|
base.OnCreate(packageName, rootDirectory);
|
||||||
|
|
||||||
|
// 重写保存根目录和临时目录
|
||||||
|
_cacheBundleFilesRoot = PathUtility.Combine(_packageRoot, "UnpackBundleFiles");
|
||||||
|
_cacheManifestFilesRoot = PathUtility.Combine(_packageRoot, "UnpackManifestFiles");
|
||||||
|
_tempFilesRoot = PathUtility.Combine(_packageRoot, "UnpackTempFiles");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 继承的功能
|
||||||
|
|
||||||
|
由于继承自 `DefaultCacheFileSystem`,DefaultUnpackFileSystem 拥有以下完整功能:
|
||||||
|
|
||||||
|
| 功能 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| 文件下载 | 通过 DownloadScheduler 下载资源 |
|
||||||
|
| 断点续传 | 支持大文件断点续传 |
|
||||||
|
| 文件验证 | 多线程 CRC/Hash 验证 |
|
||||||
|
| 缓存管理 | 记录已解压文件,避免重复解压 |
|
||||||
|
| 加密支持 | 支持 IDecryptionServices 解密 |
|
||||||
|
| 覆盖安装检测 | App 版本变更时清理解压缓存 |
|
||||||
|
|
||||||
|
### DefaultUnpackFileSystemDefine
|
||||||
|
|
||||||
|
常量定义类,定义解压目录名称。
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
internal class DefaultUnpackFileSystemDefine
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 保存的资源文件的文件夹名称
|
||||||
|
/// </summary>
|
||||||
|
public const string SaveBundleFilesFolderName = "UnpackBundleFiles";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 保存的清单文件的文件夹名称
|
||||||
|
/// </summary>
|
||||||
|
public const string SaveManifestFilesFolderName = "UnpackManifestFiles";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 下载的临时文件的文件夹名称
|
||||||
|
/// </summary>
|
||||||
|
public const string TempFilesFolderName = "UnpackTempFiles";
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### DefaultUnpackRemoteServices
|
||||||
|
|
||||||
|
本地资源服务接口,将 StreamingAssets 路径转换为 WWW 请求路径。
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
internal class DefaultUnpackRemoteServices : IRemoteServices
|
||||||
|
{
|
||||||
|
private readonly string _buildinPackageRoot;
|
||||||
|
protected readonly Dictionary<string, string> _mapping = new Dictionary<string, string>(10000);
|
||||||
|
|
||||||
|
public DefaultUnpackRemoteServices(string buildinPackRoot)
|
||||||
|
{
|
||||||
|
_buildinPackageRoot = buildinPackRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 主地址和备用地址相同(本地文件)
|
||||||
|
string IRemoteServices.GetRemoteMainURL(string fileName)
|
||||||
|
{
|
||||||
|
return GetFileLoadURL(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
string IRemoteServices.GetRemoteFallbackURL(string fileName)
|
||||||
|
{
|
||||||
|
return GetFileLoadURL(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetFileLoadURL(string fileName)
|
||||||
|
{
|
||||||
|
if (_mapping.TryGetValue(fileName, out string url) == false)
|
||||||
|
{
|
||||||
|
string filePath = PathUtility.Combine(_buildinPackageRoot, fileName);
|
||||||
|
url = DownloadSystemHelper.ConvertToWWWPath(filePath);
|
||||||
|
_mapping.Add(fileName, url);
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 路径转换示例
|
||||||
|
|
||||||
|
```
|
||||||
|
输入文件名: bundle_abc123.bundle
|
||||||
|
|
||||||
|
StreamingAssets 路径:
|
||||||
|
{Application.streamingAssetsPath}/DefaultPackage/bundle_abc123.bundle
|
||||||
|
|
||||||
|
WWW 请求路径 (Android):
|
||||||
|
jar:file:///data/app/com.example.game.apk!/assets/DefaultPackage/bundle_abc123.bundle
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 目录结构
|
||||||
|
|
||||||
|
### 与 DefaultCacheFileSystem 对比
|
||||||
|
|
||||||
|
| 目录类型 | DefaultCacheFileSystem | DefaultUnpackFileSystem |
|
||||||
|
|----------|----------------------|------------------------|
|
||||||
|
| 资源文件 | `BundleFiles/` | `UnpackBundleFiles/` |
|
||||||
|
| 清单文件 | `ManifestFiles/` | `UnpackManifestFiles/` |
|
||||||
|
| 临时文件 | `TempFiles/` | `UnpackTempFiles/` |
|
||||||
|
|
||||||
|
### 实际目录结构
|
||||||
|
|
||||||
|
```
|
||||||
|
{SandboxRoot}/{PackageName}/
|
||||||
|
├── BundleFiles/ # DefaultCacheFileSystem 下载缓存
|
||||||
|
│ └── ...
|
||||||
|
├── ManifestFiles/ # DefaultCacheFileSystem 清单缓存
|
||||||
|
│ └── ...
|
||||||
|
├── UnpackBundleFiles/ # DefaultUnpackFileSystem 解压缓存
|
||||||
|
│ ├── {Hash[0:2]}/
|
||||||
|
│ │ └── {BundleGUID}/
|
||||||
|
│ │ ├── __data
|
||||||
|
│ │ └── __info
|
||||||
|
│ └── ...
|
||||||
|
├── UnpackManifestFiles/ # DefaultUnpackFileSystem 清单
|
||||||
|
│ └── ...
|
||||||
|
└── UnpackTempFiles/ # DefaultUnpackFileSystem 临时文件
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 工作原理
|
||||||
|
|
||||||
|
### 解压触发条件
|
||||||
|
|
||||||
|
在 `DefaultBuildinFileSystem` 中,以下情况会触发解压:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
protected virtual bool IsUnpackBundleFile(PackageBundle bundle)
|
||||||
|
{
|
||||||
|
if (Belong(bundle) == false)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
#if UNITY_ANDROID || UNITY_OPENHARMONY
|
||||||
|
// Android/OpenHarmony 平台
|
||||||
|
if (bundle.Encrypted)
|
||||||
|
return true; // 加密资源需要解压
|
||||||
|
|
||||||
|
if (bundle.BundleType == (int)EBuildBundleType.RawBundle)
|
||||||
|
return true; // 原生文件需要解压
|
||||||
|
|
||||||
|
return false; // 普通 AssetBundle 不需要解压
|
||||||
|
#else
|
||||||
|
return false; // 其他平台不需要解压
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 解压流程
|
||||||
|
|
||||||
|
```
|
||||||
|
DefaultBuildinFileSystem
|
||||||
|
│
|
||||||
|
├── NeedUnpack(bundle) 检查
|
||||||
|
│ └── IsUnpackBundleFile(bundle)
|
||||||
|
│ ├── Android/OpenHarmony 平台
|
||||||
|
│ │ ├── 加密资源 → true
|
||||||
|
│ │ └── RawBundle → true
|
||||||
|
│ └── 其他平台 → false
|
||||||
|
│
|
||||||
|
├── 需要解压时
|
||||||
|
│ └── _unpackFileSystem.DownloadFileAsync(bundle, options)
|
||||||
|
│ └── DefaultUnpackRemoteServices.GetRemoteMainURL()
|
||||||
|
│ └── 返回 StreamingAssets 的 WWW 路径
|
||||||
|
│ ↓
|
||||||
|
│ └── DownloadAndCacheRemoteFileOperation
|
||||||
|
│ ├── 从 APK 内 "下载" 资源
|
||||||
|
│ ├── 验证文件完整性
|
||||||
|
│ └── 保存到 UnpackBundleFiles 目录
|
||||||
|
│
|
||||||
|
└── 加载资源时
|
||||||
|
└── _unpackFileSystem.LoadBundleFile(bundle)
|
||||||
|
└── 从 UnpackBundleFiles 加载
|
||||||
|
```
|
||||||
|
|
||||||
|
### 资源加载委托
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// DefaultBuildinFileSystem.LoadBundleFile()
|
||||||
|
public virtual FSLoadBundleOperation LoadBundleFile(PackageBundle bundle)
|
||||||
|
{
|
||||||
|
// 需要解压的资源,委托给解压文件系统加载
|
||||||
|
if (IsUnpackBundleFile(bundle))
|
||||||
|
{
|
||||||
|
return _unpackFileSystem.LoadBundleFile(bundle);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 普通资源直接从 StreamingAssets 加载
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 与 DefaultBuildinFileSystem 集成
|
||||||
|
|
||||||
|
### 初始化流程
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// DefaultBuildinFileSystem.OnCreate()
|
||||||
|
public virtual void OnCreate(string packageName, string packageRoot)
|
||||||
|
{
|
||||||
|
// ... 基础初始化 ...
|
||||||
|
|
||||||
|
// 创建解压文件系统
|
||||||
|
var remoteServices = new DefaultUnpackRemoteServices(_packageRoot);
|
||||||
|
_unpackFileSystem = new DefaultUnpackFileSystem();
|
||||||
|
|
||||||
|
// 传递配置参数
|
||||||
|
_unpackFileSystem.SetParameter(REMOTE_SERVICES, remoteServices);
|
||||||
|
_unpackFileSystem.SetParameter(INSTALL_CLEAR_MODE, InstallClearMode);
|
||||||
|
_unpackFileSystem.SetParameter(FILE_VERIFY_LEVEL, FileVerifyLevel);
|
||||||
|
_unpackFileSystem.SetParameter(FILE_VERIFY_MAX_CONCURRENCY, FileVerifyMaxConcurrency);
|
||||||
|
_unpackFileSystem.SetParameter(APPEND_FILE_EXTENSION, AppendFileExtension);
|
||||||
|
_unpackFileSystem.SetParameter(DECRYPTION_SERVICES, DecryptionServices);
|
||||||
|
_unpackFileSystem.SetParameter(COPY_LOCAL_FILE_SERVICES, CopyLocalFileServices);
|
||||||
|
|
||||||
|
// 使用指定的解压根目录
|
||||||
|
_unpackFileSystem.OnCreate(packageName, UnpackFileSystemRoot);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 方法委托关系
|
||||||
|
|
||||||
|
| DefaultBuildinFileSystem 方法 | 解压资源时的委托目标 |
|
||||||
|
|------------------------------|-------------------|
|
||||||
|
| `LoadBundleFile()` | `_unpackFileSystem.LoadBundleFile()` |
|
||||||
|
| `GetBundleFilePath()` | `_unpackFileSystem.GetBundleFilePath()` |
|
||||||
|
| `ReadBundleFileData()` | `_unpackFileSystem.ReadBundleFileData()` |
|
||||||
|
| `ReadBundleFileText()` | `_unpackFileSystem.ReadBundleFileText()` |
|
||||||
|
| `DownloadFileAsync()` | `_unpackFileSystem.DownloadFileAsync()` |
|
||||||
|
| `ClearCacheFilesAsync()` | `_unpackFileSystem.ClearCacheFilesAsync()` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 使用场景
|
||||||
|
|
||||||
|
### 场景 1:Android 加密资源
|
||||||
|
|
||||||
|
```
|
||||||
|
问题:Android 平台无法直接从 APK 内读取文件进行解密
|
||||||
|
解决:先解压到沙盒,再从沙盒读取并解密
|
||||||
|
|
||||||
|
流程:
|
||||||
|
1. 检测到加密资源 → IsUnpackBundleFile() = true
|
||||||
|
2. 首次加载 → NeedUnpack() = true
|
||||||
|
3. 执行解压 → DownloadFileAsync() 从 APK 复制到沙盒
|
||||||
|
4. 后续加载 → NeedUnpack() = false,直接从沙盒加载
|
||||||
|
```
|
||||||
|
|
||||||
|
### 场景 2:Android 原生文件
|
||||||
|
|
||||||
|
```
|
||||||
|
问题:RawBundle 需要通过文件路径访问,APK 内路径不可直接访问
|
||||||
|
解决:解压到沙盒,返回沙盒内的文件路径
|
||||||
|
|
||||||
|
流程:
|
||||||
|
1. 检测到 RawBundle → IsUnpackBundleFile() = true
|
||||||
|
2. 首次访问 → 解压到 UnpackBundleFiles
|
||||||
|
3. GetBundleFilePath() 返回沙盒路径
|
||||||
|
4. 业务代码使用标准文件 API 访问
|
||||||
|
```
|
||||||
|
|
||||||
|
### 场景 3:普通 AssetBundle
|
||||||
|
|
||||||
|
```
|
||||||
|
情况:Android 平台的普通(未加密)AssetBundle
|
||||||
|
处理:不需要解压,Unity 可以直接从 APK 内加载
|
||||||
|
|
||||||
|
流程:
|
||||||
|
1. IsUnpackBundleFile() = false
|
||||||
|
2. 直接使用 AssetBundle.LoadFromFile() 加载
|
||||||
|
3. Unity 内部处理 APK 访问
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 类继承关系
|
||||||
|
|
||||||
|
```
|
||||||
|
IFileSystem
|
||||||
|
└── DefaultCacheFileSystem
|
||||||
|
└── DefaultUnpackFileSystem ← 仅重写目录名称
|
||||||
|
|
||||||
|
IRemoteServices
|
||||||
|
└── DefaultUnpackRemoteServices ← 本地 WWW 路径服务
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 配置参数
|
||||||
|
|
||||||
|
DefaultUnpackFileSystem 继承 DefaultCacheFileSystem 的所有参数:
|
||||||
|
|
||||||
|
| 参数 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `REMOTE_SERVICES` | `IRemoteServices` | 由 DefaultUnpackRemoteServices 提供 |
|
||||||
|
| `INSTALL_CLEAR_MODE` | `EOverwriteInstallClearMode` | 覆盖安装清理模式 |
|
||||||
|
| `FILE_VERIFY_LEVEL` | `EFileVerifyLevel` | 文件验证级别 |
|
||||||
|
| `FILE_VERIFY_MAX_CONCURRENCY` | `int` | 验证并发数 |
|
||||||
|
| `APPEND_FILE_EXTENSION` | `bool` | 追加文件扩展名 |
|
||||||
|
| `DECRYPTION_SERVICES` | `IDecryptionServices` | 解密服务 |
|
||||||
|
| `COPY_LOCAL_FILE_SERVICES` | `ICopyLocalFileServices` | 本地拷贝服务 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **内部组件**:DefaultUnpackFileSystem 是 DefaultBuildinFileSystem 的内部组件,不建议单独使用
|
||||||
|
2. **平台限定**:解压功能仅在 Android 和 OpenHarmony 平台生效
|
||||||
|
3. **存储占用**:解压会额外占用设备存储空间(相当于资源的两份拷贝)
|
||||||
|
4. **首次加载**:需要解压的资源首次加载会有额外耗时
|
||||||
|
5. **自动管理**:解压缓存由系统自动管理,包括覆盖安装时的清理
|
||||||
|
6. **继承完整性**:继承了 DefaultCacheFileSystem 的所有功能,包括断点续传、多线程验证等
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 与其他文件系统对比
|
||||||
|
|
||||||
|
| 特性 | DefaultUnpackFileSystem | DefaultCacheFileSystem |
|
||||||
|
|------|------------------------|----------------------|
|
||||||
|
| 数据来源 | StreamingAssets (本地) | 远程服务器 |
|
||||||
|
| 主/备用地址 | 相同(本地路径) | 不同(CDN 地址) |
|
||||||
|
| 存储目录 | UnpackXxxFiles | XxxFiles |
|
||||||
|
| 使用方式 | 作为内部组件 | 独立使用 |
|
||||||
|
| 继承关系 | 子类 | 父类 |
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 402224e40b04b0d458f12f5925d229b7
|
||||||
|
TextScriptImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -10,6 +10,11 @@ namespace YooAsset
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal class DefaultWebRemoteFileSystem : IFileSystem
|
internal class DefaultWebRemoteFileSystem : IFileSystem
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 下载后台接口
|
||||||
|
/// </summary>
|
||||||
|
public IDownloadBackend DownloadBackend { private set; get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 包裹名称
|
/// 包裹名称
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -38,6 +43,11 @@ namespace YooAsset
|
|||||||
}
|
}
|
||||||
|
|
||||||
#region 自定义参数
|
#region 自定义参数
|
||||||
|
/// <summary>
|
||||||
|
/// 自定义参数:UnityWebRequest 创建委托
|
||||||
|
/// </summary>
|
||||||
|
public UnityWebRequestCreator WebRequestCreator { private set; get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 禁用Unity的网络缓存
|
/// 禁用Unity的网络缓存
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -104,7 +114,15 @@ namespace YooAsset
|
|||||||
|
|
||||||
public virtual void SetParameter(string name, object value)
|
public virtual void SetParameter(string name, object value)
|
||||||
{
|
{
|
||||||
if (name == FileSystemParametersDefine.DISABLE_UNITY_WEB_CACHE)
|
if (name == FileSystemParametersDefine.DOWNLOAD_BACKEND)
|
||||||
|
{
|
||||||
|
DownloadBackend = (IDownloadBackend)value;
|
||||||
|
}
|
||||||
|
else if (name == FileSystemParametersDefine.UNITY_WEB_REQUEST_CREATOR)
|
||||||
|
{
|
||||||
|
WebRequestCreator = (UnityWebRequestCreator)value;
|
||||||
|
}
|
||||||
|
else if (name == FileSystemParametersDefine.DISABLE_UNITY_WEB_CACHE)
|
||||||
{
|
{
|
||||||
DisableUnityWebCache = Convert.ToBoolean(value);
|
DisableUnityWebCache = Convert.ToBoolean(value);
|
||||||
}
|
}
|
||||||
@@ -128,9 +146,18 @@ namespace YooAsset
|
|||||||
public virtual void OnCreate(string packageName, string packageRoot)
|
public virtual void OnCreate(string packageName, string packageRoot)
|
||||||
{
|
{
|
||||||
PackageName = packageName;
|
PackageName = packageName;
|
||||||
|
|
||||||
|
// 创建默认的下载后台接口
|
||||||
|
if (DownloadBackend == null)
|
||||||
|
DownloadBackend = new UnityWebRequestBackend(WebRequestCreator);
|
||||||
}
|
}
|
||||||
public virtual void OnDestroy()
|
public virtual void OnDestroy()
|
||||||
{
|
{
|
||||||
|
if (DownloadBackend != null)
|
||||||
|
{
|
||||||
|
DownloadBackend.Dispose();
|
||||||
|
DownloadBackend = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual bool Belong(PackageBundle bundle)
|
public virtual bool Belong(PackageBundle bundle)
|
||||||
|
|||||||
@@ -41,13 +41,13 @@ namespace YooAsset
|
|||||||
|
|
||||||
if (_bundle.Encrypted)
|
if (_bundle.Encrypted)
|
||||||
{
|
{
|
||||||
_loadWebAssetBundleOp = new LoadWebEncryptAssetBundleOperation(_bundle, options, _fileSystem.DecryptionServices);
|
_loadWebAssetBundleOp = new LoadWebEncryptAssetBundleOperation(_bundle, options, _fileSystem.DecryptionServices, _fileSystem.DownloadBackend);
|
||||||
_loadWebAssetBundleOp.StartOperation();
|
_loadWebAssetBundleOp.StartOperation();
|
||||||
AddChildOperation(_loadWebAssetBundleOp);
|
AddChildOperation(_loadWebAssetBundleOp);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_loadWebAssetBundleOp = new LoadWebNormalAssetBundleOperation(_bundle, options, _fileSystem.DisableUnityWebCache);
|
_loadWebAssetBundleOp = new LoadWebNormalAssetBundleOperation(_bundle, options, _fileSystem.DisableUnityWebCache, _fileSystem.DownloadBackend);
|
||||||
_loadWebAssetBundleOp.StartOperation();
|
_loadWebAssetBundleOp.StartOperation();
|
||||||
AddChildOperation(_loadWebAssetBundleOp);
|
AddChildOperation(_loadWebAssetBundleOp);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ namespace YooAsset
|
|||||||
{
|
{
|
||||||
if (_requestWebPackageHashOp == null)
|
if (_requestWebPackageHashOp == null)
|
||||||
{
|
{
|
||||||
_requestWebPackageHashOp = new RequestWebPackageHashOperation(_fileSystem.RemoteServices, _fileSystem.PackageName, _packageVersion, _timeout);
|
_requestWebPackageHashOp = new RequestWebPackageHashOperation(_fileSystem.RemoteServices, _fileSystem.DownloadBackend, _fileSystem.PackageName, _packageVersion, _timeout);
|
||||||
_requestWebPackageHashOp.StartOperation();
|
_requestWebPackageHashOp.StartOperation();
|
||||||
AddChildOperation(_requestWebPackageHashOp);
|
AddChildOperation(_requestWebPackageHashOp);
|
||||||
}
|
}
|
||||||
@@ -67,7 +67,8 @@ namespace YooAsset
|
|||||||
string packageName = _fileSystem.PackageName;
|
string packageName = _fileSystem.PackageName;
|
||||||
var manifestServices = _fileSystem.ManifestServices;
|
var manifestServices = _fileSystem.ManifestServices;
|
||||||
var remoteServices = _fileSystem.RemoteServices;
|
var remoteServices = _fileSystem.RemoteServices;
|
||||||
_loadWebPackageManifestOp = new LoadWebPackageManifestOperation(manifestServices, remoteServices, packageName, _packageVersion, packageHash, _timeout);
|
var downloadBackend = _fileSystem.DownloadBackend;
|
||||||
|
_loadWebPackageManifestOp = new LoadWebPackageManifestOperation(manifestServices, remoteServices, downloadBackend, packageName, _packageVersion, packageHash, _timeout);
|
||||||
_loadWebPackageManifestOp.StartOperation();
|
_loadWebPackageManifestOp.StartOperation();
|
||||||
AddChildOperation(_loadWebPackageManifestOp);
|
AddChildOperation(_loadWebPackageManifestOp);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ namespace YooAsset
|
|||||||
{
|
{
|
||||||
if (_requestWebPackageVersionOp == null)
|
if (_requestWebPackageVersionOp == null)
|
||||||
{
|
{
|
||||||
_requestWebPackageVersionOp = new RequestWebPackageVersionOperation(_fileSystem.RemoteServices, _fileSystem.PackageName, _appendTimeTicks, _timeout);
|
_requestWebPackageVersionOp = new RequestWebPackageVersionOperation(_fileSystem.RemoteServices, _fileSystem.DownloadBackend, _fileSystem.PackageName, _appendTimeTicks, _timeout);
|
||||||
_requestWebPackageVersionOp.StartOperation();
|
_requestWebPackageVersionOp.StartOperation();
|
||||||
AddChildOperation(_requestWebPackageVersionOp);
|
AddChildOperation(_requestWebPackageVersionOp);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,504 @@
|
|||||||
|
# DefaultWebRemoteFileSystem Web远程文件系统
|
||||||
|
|
||||||
|
## 模块概述
|
||||||
|
|
||||||
|
DefaultWebRemoteFileSystem 是 YooAsset 的 **Web 远程文件系统**,专为从远程服务器直接加载资源而设计。该文件系统不缓存文件到本地,每次都从远程 URL 加载资源,适用于 WebGL 平台的跨域资源加载或特殊的网络资源场景。
|
||||||
|
|
||||||
|
### 核心特性
|
||||||
|
|
||||||
|
- **无本地缓存**:直接从远程 URL 加载,不写入本地文件
|
||||||
|
- **跨域支持**:通过 `IRemoteServices` 支持跨域资源下载
|
||||||
|
- **Unity 缓存控制**:可选择禁用 Unity 的 Web 请求缓存
|
||||||
|
- **加密支持**:支持 `IWebDecryptionServices` 解密 Web 资源
|
||||||
|
- **失败重试**:内置下载失败自动重试机制
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 设计目标
|
||||||
|
|
||||||
|
| 目标 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| **轻量级** | 无缓存管理,结构简洁 |
|
||||||
|
| **即时加载** | 每次从远程获取最新资源 |
|
||||||
|
| **跨域兼容** | 支持 WebGL 平台的跨域限制处理 |
|
||||||
|
| **可配置** | 支持 Unity Web 缓存控制和自定义解密 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 文件结构
|
||||||
|
|
||||||
|
```
|
||||||
|
DefaultWebRemoteFileSystem/
|
||||||
|
├── DefaultWebRemoteFileSystem.cs # 文件系统主类
|
||||||
|
└── Operation/ # 操作类
|
||||||
|
├── DWRFSInitializeOperation.cs # 初始化操作
|
||||||
|
├── DWRFSRequestPackageVersionOperation.cs # 请求版本操作
|
||||||
|
├── DWRFSLoadPackageManifestOperation.cs # 加载清单操作
|
||||||
|
└── DWRFSLoadBundleOperation.cs # 加载资源包操作
|
||||||
|
```
|
||||||
|
|
||||||
|
### 依赖的共享模块
|
||||||
|
|
||||||
|
DefaultWebRemoteFileSystem 依赖 `WebGame` 目录下的共享操作类:
|
||||||
|
|
||||||
|
```
|
||||||
|
FileSystem/WebGame/Operation/
|
||||||
|
├── LoadWebAssetBundleOperation.cs # Web 资源包加载基类
|
||||||
|
├── LoadWebNormalAssetBundleOperation.cs # 普通资源包加载
|
||||||
|
├── LoadWebEncryptAssetBundleOperation.cs # 加密资源包加载
|
||||||
|
├── RequestWebPackageVersionOperation.cs # 请求版本文件
|
||||||
|
├── RequestWebPackageHashOperation.cs # 请求哈希文件
|
||||||
|
└── LoadWebPackageManifestOperation.cs # 加载清单文件
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 核心类说明
|
||||||
|
|
||||||
|
### DefaultWebRemoteFileSystem
|
||||||
|
|
||||||
|
Web 远程文件系统的主类,实现 `IFileSystem` 接口。
|
||||||
|
|
||||||
|
#### 基本属性
|
||||||
|
|
||||||
|
| 属性 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `PackageName` | `string` | 包裹名称 |
|
||||||
|
| `FileRoot` | `string` | 始终返回空字符串(无本地存储) |
|
||||||
|
| `FileCount` | `int` | 始终返回 0(无本地文件) |
|
||||||
|
| `DownloadBackend` | `IDownloadBackend` | 下载后台接口 |
|
||||||
|
|
||||||
|
#### 自定义参数
|
||||||
|
|
||||||
|
| 参数 | 类型 | 默认值 | 说明 |
|
||||||
|
|------|------|--------|------|
|
||||||
|
| `DisableUnityWebCache` | `bool` | `false` | 禁用 Unity 的网络缓存 |
|
||||||
|
| `RemoteServices` | `IRemoteServices` | - | 远程服务接口(必需) |
|
||||||
|
| `DecryptionServices` | `IWebDecryptionServices` | `null` | Web 解密服务接口 |
|
||||||
|
| `ManifestServices` | `IManifestRestoreServices` | `null` | 清单服务接口 |
|
||||||
|
|
||||||
|
#### 核心方法
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 生命周期
|
||||||
|
void OnCreate(string packageName, string packageRoot);
|
||||||
|
void OnDestroy();
|
||||||
|
void SetParameter(string name, object value);
|
||||||
|
|
||||||
|
// 异步操作
|
||||||
|
FSInitializeFileSystemOperation InitializeFileSystemAsync();
|
||||||
|
FSRequestPackageVersionOperation RequestPackageVersionAsync(bool appendTimeTicks, int timeout);
|
||||||
|
FSLoadPackageManifestOperation LoadPackageManifestAsync(string packageVersion, int timeout);
|
||||||
|
FSLoadBundleOperation LoadBundleFile(PackageBundle bundle);
|
||||||
|
FSClearCacheFilesOperation ClearCacheFilesAsync(...); // 直接返回完成
|
||||||
|
|
||||||
|
// 不支持的操作
|
||||||
|
FSDownloadFileOperation DownloadFileAsync(...); // 抛出 NotImplementedException
|
||||||
|
string GetBundleFilePath(...); // 抛出 NotImplementedException
|
||||||
|
byte[] ReadBundleFileData(...); // 抛出 NotImplementedException
|
||||||
|
string ReadBundleFileText(...); // 抛出 NotImplementedException
|
||||||
|
|
||||||
|
// 文件查询(固定返回值)
|
||||||
|
bool Belong(PackageBundle bundle); // 始终返回 true
|
||||||
|
bool Exists(PackageBundle bundle); // 始终返回 true
|
||||||
|
bool NeedDownload(PackageBundle bundle);// 始终返回 false
|
||||||
|
bool NeedUnpack(PackageBundle bundle); // 始终返回 false
|
||||||
|
bool NeedImport(PackageBundle bundle); // 始终返回 false
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 操作类说明
|
||||||
|
|
||||||
|
### DWRFSInitializeOperation
|
||||||
|
|
||||||
|
初始化操作,立即完成(无需任何初始化工作)。
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
internal override void InternalStart()
|
||||||
|
{
|
||||||
|
Status = EOperationStatus.Succeed; // 直接成功
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### DWRFSRequestPackageVersionOperation
|
||||||
|
|
||||||
|
请求包裹版本操作,从远程服务器获取版本文件。
|
||||||
|
|
||||||
|
```
|
||||||
|
状态流程:
|
||||||
|
RequestPackageVersion
|
||||||
|
└── RequestWebPackageVersionOperation
|
||||||
|
└── 请求 {PackageName}_Version.txt
|
||||||
|
├── 成功 → PackageVersion = 文件内容 → Succeed
|
||||||
|
└── 失败 → Failed
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 请求地址轮换
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 轮流使用主地址和备用地址
|
||||||
|
if (_requestCount % 2 == 0)
|
||||||
|
url = _remoteServices.GetRemoteMainURL(fileName);
|
||||||
|
else
|
||||||
|
url = _remoteServices.GetRemoteFallbackURL(fileName);
|
||||||
|
|
||||||
|
// 可选:添加时间戳防止缓存
|
||||||
|
if (_appendTimeTicks)
|
||||||
|
return $"{url}?{System.DateTime.UtcNow.Ticks}";
|
||||||
|
```
|
||||||
|
|
||||||
|
### DWRFSLoadPackageManifestOperation
|
||||||
|
|
||||||
|
加载资源清单操作,从远程下载并解析清单。
|
||||||
|
|
||||||
|
```
|
||||||
|
状态流程:
|
||||||
|
RequestWebPackageHash
|
||||||
|
└── RequestWebPackageHashOperation
|
||||||
|
└── 请求 {PackageName}_{Version}.hash
|
||||||
|
├── 成功 → PackageHash
|
||||||
|
└── 失败 → Failed
|
||||||
|
↓
|
||||||
|
LoadWebPackageManifest
|
||||||
|
└── LoadWebPackageManifestOperation
|
||||||
|
└── 请求 {PackageName}_{Version}.bytes
|
||||||
|
├── 验证哈希
|
||||||
|
└── 反序列化清单
|
||||||
|
├── 成功 → Manifest → Succeed
|
||||||
|
└── 失败 → Failed
|
||||||
|
```
|
||||||
|
|
||||||
|
### DWRFSLoadAssetBundleOperation
|
||||||
|
|
||||||
|
加载资源包操作,从远程 URL 直接加载 AssetBundle。
|
||||||
|
|
||||||
|
```
|
||||||
|
状态流程:
|
||||||
|
LoadWebAssetBundle
|
||||||
|
├── 未加密 → LoadWebNormalAssetBundleOperation
|
||||||
|
│ └── UnityWebRequestAssetBundle.GetAssetBundle()
|
||||||
|
│ ├── 成功 → AssetBundle → AssetBundleResult
|
||||||
|
│ └── 失败 → TryAgain 或 Failed
|
||||||
|
│
|
||||||
|
└── 已加密 → LoadWebEncryptAssetBundleOperation
|
||||||
|
└── DownloadBytesRequest
|
||||||
|
└── 下载原始字节
|
||||||
|
└── IWebDecryptionServices.LoadAssetBundle()
|
||||||
|
├── 成功 → AssetBundle → AssetBundleResult
|
||||||
|
└── 失败 → TryAgain 或 Failed
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 状态机枚举
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
private enum ESteps
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
LoadWebAssetBundle, // 加载 Web 资源包
|
||||||
|
Done // 完成
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 同步加载限制
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
internal override void InternalWaitForAsyncComplete()
|
||||||
|
{
|
||||||
|
if (_steps != ESteps.Done)
|
||||||
|
{
|
||||||
|
_steps = ESteps.Done;
|
||||||
|
Status = EOperationStatus.Failed;
|
||||||
|
Error = "WebGL platform not support sync load method !";
|
||||||
|
UnityEngine.Debug.LogError(Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 共享 Web 操作类
|
||||||
|
|
||||||
|
### LoadWebNormalAssetBundleOperation
|
||||||
|
|
||||||
|
普通(未加密)AssetBundle 的 Web 加载操作。
|
||||||
|
|
||||||
|
```
|
||||||
|
CreateRequest
|
||||||
|
└── DownloadAssetBundleRequest
|
||||||
|
├── URL: 主地址或备用地址(轮换)
|
||||||
|
├── DisableUnityWebCache: 是否禁用缓存
|
||||||
|
├── FileHash: 用于 Unity 缓存键
|
||||||
|
└── UnityCRC: CRC 验证
|
||||||
|
↓
|
||||||
|
CheckRequest
|
||||||
|
├── 成功 → Result = AssetBundle
|
||||||
|
└── 失败 → TryAgain(重试)或 Failed
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Unity Web 缓存机制
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 使用 Unity 的内置缓存
|
||||||
|
var args = new DownloadAssetBundleRequestArgs(
|
||||||
|
url,
|
||||||
|
timeout: 0,
|
||||||
|
watchdogTime: 0,
|
||||||
|
disableUnityWebCache: _disableUnityWebCache,
|
||||||
|
cacheHash: _bundle.FileHash, // 缓存键
|
||||||
|
unityCRC: _bundle.UnityCRC // CRC 验证
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### LoadWebEncryptAssetBundleOperation
|
||||||
|
|
||||||
|
加密 AssetBundle 的 Web 加载操作。
|
||||||
|
|
||||||
|
```
|
||||||
|
CreateRequest
|
||||||
|
└── 检查 DecryptionServices
|
||||||
|
├── null → Failed
|
||||||
|
└── 有效 → DownloadBytesRequest
|
||||||
|
↓
|
||||||
|
CheckRequest
|
||||||
|
├── 下载成功 → LoadEncryptedAssetBundle()
|
||||||
|
│ └── IWebDecryptionServices.LoadAssetBundle(fileData)
|
||||||
|
│ ├── 解密成功 → Result = AssetBundle
|
||||||
|
│ └── 解密失败 → Failed
|
||||||
|
└── 下载失败 → TryAgain 或 Failed
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 加密资源加载
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
private AssetBundle LoadEncryptedAssetBundle(byte[] fileData)
|
||||||
|
{
|
||||||
|
var fileInfo = new WebDecryptFileInfo();
|
||||||
|
fileInfo.BundleName = _bundle.BundleName;
|
||||||
|
fileInfo.FileLoadCRC = _bundle.UnityCRC;
|
||||||
|
fileInfo.FileData = fileData; // 下载的原始字节
|
||||||
|
var decryptResult = _decryptionServices.LoadAssetBundle(fileInfo);
|
||||||
|
return decryptResult.Result;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 失败重试机制
|
||||||
|
|
||||||
|
Web 加载操作内置失败重试机制:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 检测下载结果
|
||||||
|
if (_unityAssetBundleRequestOp.Status == EDownloadRequestStatus.Succeed)
|
||||||
|
{
|
||||||
|
_steps = ESteps.Done;
|
||||||
|
Status = EOperationStatus.Succeed;
|
||||||
|
Result = _unityAssetBundleRequestOp.Result;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (_failedTryAgain > 0)
|
||||||
|
{
|
||||||
|
_steps = ESteps.TryAgain;
|
||||||
|
YooLogger.Warning($"Failed download : {url} Try again !");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_steps = ESteps.Done;
|
||||||
|
Status = EOperationStatus.Failed;
|
||||||
|
Error = _unityAssetBundleRequestOp.Error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重新尝试下载(1秒后)
|
||||||
|
if (_steps == ESteps.TryAgain)
|
||||||
|
{
|
||||||
|
_tryAgainTimer += Time.unscaledDeltaTime;
|
||||||
|
if (_tryAgainTimer > 1f)
|
||||||
|
{
|
||||||
|
_tryAgainTimer = 0f;
|
||||||
|
_failedTryAgain--;
|
||||||
|
_steps = ESteps.CreateRequest; // 重新创建请求
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 使用示例
|
||||||
|
|
||||||
|
### 基础配置
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 创建远程服务接口
|
||||||
|
class GameRemoteServices : IRemoteServices
|
||||||
|
{
|
||||||
|
public string GetRemoteMainURL(string fileName)
|
||||||
|
{
|
||||||
|
return $"https://cdn.example.com/bundles/{fileName}";
|
||||||
|
}
|
||||||
|
public string GetRemoteFallbackURL(string fileName)
|
||||||
|
{
|
||||||
|
return $"https://cdn-backup.example.com/bundles/{fileName}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建 Web 远程文件系统参数
|
||||||
|
var webRemoteParams = FileSystemParameters.CreateDefaultWebRemoteFileSystemParameters(
|
||||||
|
remoteServices: new GameRemoteServices()
|
||||||
|
);
|
||||||
|
|
||||||
|
// 初始化包裹(WebGL 模式)
|
||||||
|
var initParams = new WebPlayModeParameters();
|
||||||
|
initParams.WebServerFileSystemParameters = webServerParams;
|
||||||
|
initParams.WebRemoteFileSystemParameters = webRemoteParams;
|
||||||
|
var initOp = package.InitializeAsync(initParams);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 禁用 Unity Web 缓存
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var webRemoteParams = FileSystemParameters.CreateDefaultWebRemoteFileSystemParameters(
|
||||||
|
remoteServices: new GameRemoteServices()
|
||||||
|
);
|
||||||
|
|
||||||
|
// 禁用 Unity 的 Web 请求缓存(始终获取最新资源)
|
||||||
|
webRemoteParams.AddParameter(FileSystemParametersDefine.DISABLE_UNITY_WEB_CACHE, true);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 配置 Web 解密服务
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 自定义 Web 解密服务
|
||||||
|
class GameWebDecryptionServices : IWebDecryptionServices
|
||||||
|
{
|
||||||
|
public WebDecryptResult LoadAssetBundle(WebDecryptFileInfo fileInfo)
|
||||||
|
{
|
||||||
|
// 解密下载的字节数据
|
||||||
|
byte[] decryptedData = Decrypt(fileInfo.FileData);
|
||||||
|
AssetBundle bundle = AssetBundle.LoadFromMemory(decryptedData);
|
||||||
|
return new WebDecryptResult { Result = bundle };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var webRemoteParams = FileSystemParameters.CreateDefaultWebRemoteFileSystemParameters(
|
||||||
|
remoteServices: new GameRemoteServices()
|
||||||
|
);
|
||||||
|
|
||||||
|
// 设置 Web 解密服务
|
||||||
|
webRemoteParams.AddParameter(
|
||||||
|
FileSystemParametersDefine.DECRYPTION_SERVICES,
|
||||||
|
new GameWebDecryptionServices()
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 跨域资源加载
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 跨域远程服务
|
||||||
|
class CrossDomainRemoteServices : IRemoteServices
|
||||||
|
{
|
||||||
|
private readonly string _mainDomain;
|
||||||
|
private readonly string _fallbackDomain;
|
||||||
|
|
||||||
|
public CrossDomainRemoteServices(string mainDomain, string fallbackDomain)
|
||||||
|
{
|
||||||
|
_mainDomain = mainDomain;
|
||||||
|
_fallbackDomain = fallbackDomain;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetRemoteMainURL(string fileName)
|
||||||
|
{
|
||||||
|
// 主 CDN 域名
|
||||||
|
return $"https://{_mainDomain}/assets/{fileName}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetRemoteFallbackURL(string fileName)
|
||||||
|
{
|
||||||
|
// 备用 CDN 域名
|
||||||
|
return $"https://{_fallbackDomain}/assets/{fileName}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用跨域服务
|
||||||
|
var remoteServices = new CrossDomainRemoteServices(
|
||||||
|
mainDomain: "cdn-us.example.com",
|
||||||
|
fallbackDomain: "cdn-eu.example.com"
|
||||||
|
);
|
||||||
|
|
||||||
|
var webRemoteParams = FileSystemParameters.CreateDefaultWebRemoteFileSystemParameters(
|
||||||
|
remoteServices: remoteServices
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 参数常量
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Unity 缓存控制
|
||||||
|
FileSystemParametersDefine.DISABLE_UNITY_WEB_CACHE // bool: 禁用 Unity Web 缓存
|
||||||
|
|
||||||
|
// 服务接口
|
||||||
|
FileSystemParametersDefine.REMOTE_SERVICES // IRemoteServices: 远程服务接口
|
||||||
|
FileSystemParametersDefine.DECRYPTION_SERVICES // IWebDecryptionServices: Web 解密服务
|
||||||
|
FileSystemParametersDefine.MANIFEST_SERVICES // IManifestRestoreServices: 清单服务
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 类继承关系
|
||||||
|
|
||||||
|
```
|
||||||
|
IFileSystem
|
||||||
|
└── DefaultWebRemoteFileSystem
|
||||||
|
|
||||||
|
FSInitializeFileSystemOperation
|
||||||
|
└── DWRFSInitializeOperation
|
||||||
|
|
||||||
|
FSRequestPackageVersionOperation
|
||||||
|
└── DWRFSRequestPackageVersionOperation
|
||||||
|
|
||||||
|
FSLoadPackageManifestOperation
|
||||||
|
└── DWRFSLoadPackageManifestOperation
|
||||||
|
|
||||||
|
FSLoadBundleOperation
|
||||||
|
└── DWRFSLoadAssetBundleOperation
|
||||||
|
|
||||||
|
AsyncOperationBase
|
||||||
|
├── LoadWebAssetBundleOperation (abstract)
|
||||||
|
│ ├── LoadWebNormalAssetBundleOperation
|
||||||
|
│ └── LoadWebEncryptAssetBundleOperation
|
||||||
|
├── RequestWebPackageVersionOperation
|
||||||
|
├── RequestWebPackageHashOperation
|
||||||
|
└── LoadWebPackageManifestOperation
|
||||||
|
|
||||||
|
BundleResult
|
||||||
|
└── AssetBundleResult
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 与其他文件系统对比
|
||||||
|
|
||||||
|
| 特性 | DefaultWebRemoteFileSystem | DefaultCacheFileSystem | DefaultWebServerFileSystem |
|
||||||
|
|------|---------------------------|------------------------|---------------------------|
|
||||||
|
| 本地缓存 | ❌ 无 | ✅ 有 | ❌ 无 |
|
||||||
|
| 支持 RawBundle | ❌ | ✅ | ❌ |
|
||||||
|
| 同步加载 | ❌ | ✅ | ❌ |
|
||||||
|
| 断点续传 | ❌ | ✅ | ❌ |
|
||||||
|
| 跨域支持 | ✅ | ✅ | ✅ |
|
||||||
|
| 适用场景 | WebGL 跨域 | 常规游戏 | WebGL 同域 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **仅支持 AssetBundle**:不支持 RawBundle 类型的资源加载
|
||||||
|
2. **不支持同步加载**:WebGL 平台限制,`WaitForAsyncComplete()` 会直接返回失败
|
||||||
|
3. **无本地缓存**:每次加载都从远程获取,注意网络流量
|
||||||
|
4. **部分方法未实现**:`DownloadFileAsync`、`GetBundleFilePath`、`ReadBundleFileData`、`ReadBundleFileText` 会抛出异常
|
||||||
|
5. **远程服务必需**:必须配置 `IRemoteServices` 接口
|
||||||
|
6. **Unity 缓存**:默认使用 Unity 的 Web 请求缓存,可通过参数禁用
|
||||||
|
7. **加密资源**:加密资源需要配置 `IWebDecryptionServices`(注意是 Web 专用接口,非 `IDecryptionServices`)
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 9f6744d0bcd43f84ea4279925784afb2
|
||||||
|
TextScriptImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -24,6 +24,11 @@ namespace YooAsset
|
|||||||
protected readonly Dictionary<string, string> _webFilePathMapping = new Dictionary<string, string>(10000);
|
protected readonly Dictionary<string, string> _webFilePathMapping = new Dictionary<string, string>(10000);
|
||||||
protected string _webPackageRoot = string.Empty;
|
protected string _webPackageRoot = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 下载后台接口
|
||||||
|
/// </summary>
|
||||||
|
public IDownloadBackend DownloadBackend { private set; get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 包裹名称
|
/// 包裹名称
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -52,6 +57,11 @@ namespace YooAsset
|
|||||||
}
|
}
|
||||||
|
|
||||||
#region 自定义参数
|
#region 自定义参数
|
||||||
|
/// <summary>
|
||||||
|
/// 自定义参数:UnityWebRequest 创建委托
|
||||||
|
/// </summary>
|
||||||
|
public UnityWebRequestCreator WebRequestCreator { private set; get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 禁用Unity的网络缓存
|
/// 禁用Unity的网络缓存
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -113,7 +123,15 @@ namespace YooAsset
|
|||||||
|
|
||||||
public virtual void SetParameter(string name, object value)
|
public virtual void SetParameter(string name, object value)
|
||||||
{
|
{
|
||||||
if (name == FileSystemParametersDefine.DISABLE_UNITY_WEB_CACHE)
|
if (name == FileSystemParametersDefine.DOWNLOAD_BACKEND)
|
||||||
|
{
|
||||||
|
DownloadBackend = (IDownloadBackend)value;
|
||||||
|
}
|
||||||
|
else if (name == FileSystemParametersDefine.UNITY_WEB_REQUEST_CREATOR)
|
||||||
|
{
|
||||||
|
WebRequestCreator = (UnityWebRequestCreator)value;
|
||||||
|
}
|
||||||
|
else if (name == FileSystemParametersDefine.DISABLE_UNITY_WEB_CACHE)
|
||||||
{
|
{
|
||||||
DisableUnityWebCache = Convert.ToBoolean(value);
|
DisableUnityWebCache = Convert.ToBoolean(value);
|
||||||
}
|
}
|
||||||
@@ -138,9 +156,18 @@ namespace YooAsset
|
|||||||
_webPackageRoot = GetDefaultWebPackageRoot(packageName);
|
_webPackageRoot = GetDefaultWebPackageRoot(packageName);
|
||||||
else
|
else
|
||||||
_webPackageRoot = packageRoot;
|
_webPackageRoot = packageRoot;
|
||||||
|
|
||||||
|
// 创建默认的下载后台接口
|
||||||
|
if (DownloadBackend == null)
|
||||||
|
DownloadBackend = new UnityWebRequestBackend(WebRequestCreator);
|
||||||
}
|
}
|
||||||
public virtual void OnDestroy()
|
public virtual void OnDestroy()
|
||||||
{
|
{
|
||||||
|
if (DownloadBackend != null)
|
||||||
|
{
|
||||||
|
DownloadBackend.Dispose();
|
||||||
|
DownloadBackend = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual bool Belong(PackageBundle bundle)
|
public virtual bool Belong(PackageBundle bundle)
|
||||||
|
|||||||
@@ -41,13 +41,13 @@ namespace YooAsset
|
|||||||
|
|
||||||
if (_bundle.Encrypted)
|
if (_bundle.Encrypted)
|
||||||
{
|
{
|
||||||
_loadWebAssetBundleOp = new LoadWebEncryptAssetBundleOperation(_bundle, options, _fileSystem.DecryptionServices);
|
_loadWebAssetBundleOp = new LoadWebEncryptAssetBundleOperation(_bundle, options, _fileSystem.DecryptionServices, _fileSystem.DownloadBackend);
|
||||||
_loadWebAssetBundleOp.StartOperation();
|
_loadWebAssetBundleOp.StartOperation();
|
||||||
AddChildOperation(_loadWebAssetBundleOp);
|
AddChildOperation(_loadWebAssetBundleOp);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_loadWebAssetBundleOp = new LoadWebNormalAssetBundleOperation(_bundle, options, _fileSystem.DisableUnityWebCache);
|
_loadWebAssetBundleOp = new LoadWebNormalAssetBundleOperation(_bundle, options, _fileSystem.DisableUnityWebCache, _fileSystem.DownloadBackend);
|
||||||
_loadWebAssetBundleOp.StartOperation();
|
_loadWebAssetBundleOp.StartOperation();
|
||||||
AddChildOperation(_loadWebAssetBundleOp);
|
AddChildOperation(_loadWebAssetBundleOp);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ namespace YooAsset
|
|||||||
|
|
||||||
private readonly DefaultWebServerFileSystem _fileSystem;
|
private readonly DefaultWebServerFileSystem _fileSystem;
|
||||||
private readonly int _timeout;
|
private readonly int _timeout;
|
||||||
private UnityWebDataRequestOperation _webDataRequestOp;
|
private IDownloadBytesRequest _webDataRequestOp;
|
||||||
private ESteps _steps = ESteps.None;
|
private ESteps _steps = ESteps.None;
|
||||||
|
|
||||||
internal LoadWebServerCatalogFileOperation(DefaultWebServerFileSystem fileSystem, int timeout)
|
internal LoadWebServerCatalogFileOperation(DefaultWebServerFileSystem fileSystem, int timeout)
|
||||||
@@ -37,16 +37,15 @@ namespace YooAsset
|
|||||||
{
|
{
|
||||||
string filePath = _fileSystem.GetCatalogBinaryFileLoadPath();
|
string filePath = _fileSystem.GetCatalogBinaryFileLoadPath();
|
||||||
string url = DownloadSystemHelper.ConvertToWWWPath(filePath);
|
string url = DownloadSystemHelper.ConvertToWWWPath(filePath);
|
||||||
_webDataRequestOp = new UnityWebDataRequestOperation(url, _timeout);
|
var args = new DownloadDataRequestArgs(url, _timeout, 0);
|
||||||
_webDataRequestOp.StartOperation();
|
_webDataRequestOp = _fileSystem.DownloadBackend.CreateBytesRequest(args);
|
||||||
AddChildOperation(_webDataRequestOp);
|
_webDataRequestOp.SendRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
_webDataRequestOp.UpdateOperation();
|
|
||||||
if (_webDataRequestOp.IsDone == false)
|
if (_webDataRequestOp.IsDone == false)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (_webDataRequestOp.Status == EOperationStatus.Succeed)
|
if (_webDataRequestOp.Status == EDownloadRequestStatus.Succeed)
|
||||||
{
|
{
|
||||||
_steps = ESteps.LoadCatalog;
|
_steps = ESteps.LoadCatalog;
|
||||||
}
|
}
|
||||||
@@ -81,11 +80,11 @@ namespace YooAsset
|
|||||||
_steps = ESteps.Done;
|
_steps = ESteps.Done;
|
||||||
Status = EOperationStatus.Succeed;
|
Status = EOperationStatus.Succeed;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_steps = ESteps.Done;
|
_steps = ESteps.Done;
|
||||||
Status = EOperationStatus.Failed;
|
Status = EOperationStatus.Failed;
|
||||||
Error = $"Failed to load catalog file : {e.Message}";
|
Error = $"Failed to load catalog file : {ex.Message}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user