【整合】 框架使用规则
This commit is contained in:
@@ -1,142 +1,170 @@
|
||||
# 资源管理
|
||||
|
||||
## 分类
|
||||
|
||||
- 核心框架
|
||||
|
||||
## 目的
|
||||
|
||||
管理 AssetBundle、语言资源、图集、prefab、DllBytes 等资源的加载、缓存和释放。
|
||||
资源管理负责 AssetBundle、依赖、语言资源、SpriteAtlas、内存卸载、启动代码更新和普通资源分包下载。
|
||||
|
||||
---
|
||||
## 源码边界
|
||||
|
||||
## 核心路径
|
||||
| 类型 | 路径 | 说明 |
|
||||
|---|---|---|
|
||||
| ESF AssetBundle 源码 | `Assets/StrayFog/Editor/CopyToX/CopyToHF/AssetBundle/` | 框架修改源头 |
|
||||
| HF AssetBundle 代码 | `Assets/Game/GameHFScripte/CopyToHF/AssetBundle/` | CopyToX 生成,禁止手改 |
|
||||
| V2 公共运行时 | `Assets/StrayFog/Core/SFSubPackageV2.cs` | 非热更层与热更层共享下载状态 |
|
||||
| V2 编辑器构建 | `Assets/StrayFog/Editor/Tools/AssestBundle/` | 包配置、校验、清单和枚举生成 |
|
||||
| 热更业务门面 | `Assets/Game/GameHFScripte/GameFunction/HFDownLoadSubPackage/` | 普通资源下载 API |
|
||||
| 启动代码入口 | `Assets/Game/GameMono/Scripts/GameDllManager.cs` | Bootstrap 校验、下载和加载 |
|
||||
|
||||
- AssetBundle:`Assets/Game/GameHFScripte/CopyToHF/AssetBundle/`
|
||||
- 图集:`Assets/Game/GameHFScripte/CopyToHF/SpriteAtlas/`
|
||||
- 资源目录:`Assets/Game/GameHFResource/CN/`、`EN/`、`DllBytes/`
|
||||
修改 `ESF_` 后执行 CopyToX,再检查生成的 `HF_` 差异和 `GameHF` 编译结果。
|
||||
|
||||
---
|
||||
|
||||
## 核心类
|
||||
|
||||
| 类 | 职责 |
|
||||
|---|---|
|
||||
| `HFABMgr` | AssetBundle 资源管理器单例 |
|
||||
| `HFAssetLoader` | 具体资源加载器 |
|
||||
| `HFAssetRequest` | 资源加载请求 |
|
||||
| `HFAssetBundlePathAttribute` | 资源路径属性,标记资源根目录、相对路径、语言包规则等 |
|
||||
| `HFGlobal_SpriteAtlas` | 图集全局管理 |
|
||||
|
||||
---
|
||||
|
||||
## 资源路径规则
|
||||
|
||||
`HFAssetBundlePathAttribute` 记录:
|
||||
|
||||
- 资源根目录
|
||||
- 相对路径
|
||||
- 是否忽略语言包
|
||||
- 是否场景
|
||||
- 线程优先级
|
||||
|
||||
`ChangeLanguage` 生成:
|
||||
|
||||
- `editorPath`
|
||||
- `assetBundlePath`
|
||||
- `assetBundleKey`
|
||||
- `assetName`
|
||||
|
||||
`HFABMgr.OnCheckAssetBundlePathAttributeLanguage` 在当前语言无资源时回退默认语言。
|
||||
|
||||
---
|
||||
|
||||
## 加载流程
|
||||
## AssetBundle 加载
|
||||
|
||||
```text
|
||||
HFABMgr.LoadInMemory(pathAttr)
|
||||
-> OnReadAssetBundleManifest()
|
||||
-> OnCheckAssetBundlePathAttributeLanguage()
|
||||
-> new HFAssetRequest(...)
|
||||
-> OnStartLoad()
|
||||
-> 获取或创建 HFAssetLoader
|
||||
-> HFAssetLoader.LoadAsset()
|
||||
HFFunCatalogue.Asset / HFABMgr.LoadInMemory
|
||||
-> 读取 AssetBundleManifest
|
||||
-> 语言与路径修正
|
||||
-> 创建或复用 HFAssetLoader
|
||||
-> 加载依赖 Bundle
|
||||
-> 加载主 Bundle
|
||||
-> HFAssetResponse.GetAsset<T>()
|
||||
```
|
||||
|
||||
---
|
||||
`HFABMgr` 继续作为统一入口,并使用 partial 文件组织 Manifest、Loader、GC、路径和模式逻辑。内部职责可以封装,但业务入口不拆散。
|
||||
|
||||
## 加载模式
|
||||
### Manifest 与来源顺序
|
||||
|
||||
### Editor 模式
|
||||
Editor 模式直接读取工程资源。Player 本地模式按以下顺序初始化 Manifest:
|
||||
|
||||
```text
|
||||
enHFAbLoadModel.Editor
|
||||
-> SFAssetUtility_Unity.LoadFromFile(editorPath)
|
||||
-> 直接读取编辑器资源
|
||||
PersistentData Manifest
|
||||
-> 不存在或无效时读取 StreamingAssets Manifest
|
||||
-> 两处都不可用且已配置 CDN 时读取 CDN Manifest
|
||||
```
|
||||
|
||||
### 非 Editor 模式
|
||||
- PersistentData 保存热更新后的可写资源,优先于母包资源。
|
||||
- StreamingAssets 是只读随包资源;Android、WebGL 等 URL 路径通过 UnityWebRequest 协程读取。
|
||||
- CDN 是本地 Manifest 都不可用或资源需要更新时的远端来源。
|
||||
- 显式 CDN/小游戏模式按平台适配层和缓存回调工作,不假设普通文件系统可用。
|
||||
- `Application.dataPath` 只用于 `UNITY_EDITOR` 下的资源定位和分析。
|
||||
|
||||
PersistentData 普通文件优先使用 `AssetBundle.LoadFromFileAsync`。StreamingAssets URL、CDN byte[]、小游戏缓存和需要解密的数据继续使用 `LoadFromMemoryAsync`。
|
||||
|
||||
### CDN 落盘
|
||||
|
||||
`SetCdnSaveRootPath(string)` 允许平台层传入保存根目录。有效外部路径优先;未设置时使用 `SFGlobalData.assetBundleRoot`,即项目 PersistentData 资源根目录。保存时保留相对目录结构。
|
||||
|
||||
## 内存与卸载
|
||||
|
||||
- GC 与主动卸载共用 loader 释放流程。
|
||||
- 释放时调用 `AssetBundle.Unload(false)`,销毁 loader,并同步清理 loader 映射、生命周期池和内存统计。
|
||||
- 按资源名卸载只在唯一匹配且 loader 可卸载时成功;未找到、重名、加载未完成或仍有请求时返回 `false`。
|
||||
- 内存大小使用 loader 统计或本地文件长度估算,只用于运行时观察,不替代 Unity Profiler。
|
||||
|
||||
公开使用方式见 [资源管理使用指南](使用指南.md)。
|
||||
|
||||
## 分包 V2
|
||||
|
||||
V2 将编辑器构建清单与设备下载状态分离。运行时只消费编辑器生成的 V2 清单,不识别旧 `SPI_*` 结构。
|
||||
|
||||
### 编辑器阶段
|
||||
|
||||
资源包通过 Unity 编辑器自由配置,XML 只负责持久化。每个 Package 包含:
|
||||
|
||||
- `Id`、`DisplayName`、`Priority`、`BuiltIn`
|
||||
- `RootPaths[]`
|
||||
- `DependencyPackageIds[]`
|
||||
|
||||
保存和构建会校验空/重复 ID、无效路径、自依赖、缺失依赖和循环依赖。根路径使用标准化小写路径与目录边界前缀匹配;重叠目录警告但允许构建。
|
||||
|
||||
构建顺序:
|
||||
|
||||
```text
|
||||
读取 AssetBundleManifest
|
||||
-> 解析依赖
|
||||
-> AssetBundle.LoadFromMemoryAsync 读取主资源
|
||||
普通 AssetBundle
|
||||
-> AOT 与 HotUpdate DLL 产物
|
||||
-> 收集最终文件
|
||||
-> 生成 V2 subpackage.json
|
||||
-> 复制 BuiltIn 文件并生成分析结果
|
||||
```
|
||||
|
||||
### CDN / 边玩边下
|
||||
生成清单的同时生成 `enHFSubPackageId.cs`。枚举成员由 PackageId 转换,XML 注释使用 DisplayName;业务通过映射恢复原始 PackageId,生成文件禁止手改。
|
||||
|
||||
### V2 清单
|
||||
|
||||
```text
|
||||
缺失资源进入 WaitDownload
|
||||
-> HFAssetsDownLoadManager 下载
|
||||
-> HFABMgr.SaveFile 保存到本地
|
||||
-> 加载完成
|
||||
Version / Platform / BuildId
|
||||
BootstrapFiles[] AOT 与 HotUpdate 启动代码
|
||||
Files[] 普通 AB 的 Path、MD5、Size、Dependencies
|
||||
Packages[] 包配置、FilePaths 和 Size
|
||||
```
|
||||
|
||||
---
|
||||
- `BootstrapFiles` 按 `CodeKind` 和 `LoadOrder` 控制 AOT、HotUpdate 加载顺序。
|
||||
- `Files` 以标准化 Path 全局唯一,依赖来自 Unity AssetBundleManifest。
|
||||
- `Packages` 没有固定业务类型;PackageId 的业务意义由调用方决定。
|
||||
- Package 显式依赖和 AB 文件依赖是两套关系,运行时都会递归展开。
|
||||
|
||||
## 图集加载
|
||||
### 运行时顺序
|
||||
|
||||
```text
|
||||
SpriteAtlasManager.atlasRequested
|
||||
-> HFGlobal_SpriteAtlas.Instance.GetSpriteAtlasPath(spriteAtlasName)
|
||||
-> HFABMgr.OnLoadInMemory(spriteAtlasAttribute)
|
||||
-> callback(SpriteAtlas)
|
||||
下载并校验远端 V2 清单
|
||||
-> 比对 BootstrapFiles
|
||||
-> PersistentData 命中则复用
|
||||
-> 否则检查 StreamingAssets
|
||||
-> 仍不匹配则从 CDN 下载到临时文件并校验 MD5
|
||||
-> AOT 加载成功
|
||||
-> HotUpdate 加载与入口执行成功
|
||||
-> CommitBootstrapCodeLoaded 提交清单和状态
|
||||
-> 普通资源比对与下载开放
|
||||
```
|
||||
|
||||
---
|
||||
Bootstrap 是普通资源的硬门禁。清单、下载、MD5、AOT、HotUpdate 或入口任一步失败都不会开放普通资源阶段。
|
||||
|
||||
## 使用规则
|
||||
CDN URL 使用:
|
||||
|
||||
- 资源路径统一通过 `HFAssetBundlePathAttribute` 配置。
|
||||
- 加载资源统一走 `HFFunCatalogue.Asset.LoadAsset()` 或 `HFABMgr.LoadInMemory()`。
|
||||
- 语言资源按 `GameHFResource/<lang>/` 分目录存放。
|
||||
- 图集按业务或 UI 模块划分。
|
||||
```text
|
||||
{BaseUrl}/{AppPartner}/{AppVersion}/{Platform}/{FilePath}
|
||||
```
|
||||
|
||||
---
|
||||
BaseUrl 按配置顺序尝试,使用 `/` 进行 URI 拼接。Bootstrap 文件最多三轮,每轮尝试全部有效 BaseUrl;普通文件一次请求按顺序尝试有效地址。
|
||||
|
||||
## 相关模块
|
||||
### 普通资源更新范围
|
||||
|
||||
- [UI 窗口系统](../UI窗口系统/README.md)
|
||||
- [实体系统](../实体系统/README.md)
|
||||
- [场景系统](../场景系统.md)
|
||||
`CheckAndDownloadAllFiles` 不会无条件下载整个 `Files[]`,它处理:
|
||||
|
||||
## 子文档
|
||||
- 未归属任何 Package 的基础文件。
|
||||
- `BuiltIn = true` 的 Package 及依赖包。
|
||||
- `DownloadedPackageIds` 中历史成功下载的 Package 及依赖包。
|
||||
- 上述文件的完整 AB 依赖闭包。
|
||||
|
||||
- [资源管理使用指南](使用指南.md)
|
||||
- [资源加载重构方案](资源加载重构方案.md)
|
||||
从未下载、非 BuiltIn 的可选 Package 不会被全量更新误下载。业务可按 PackageId 主动下载,资源加载缺文件时也可按 `Files[]` 下载目标文件依赖闭包。
|
||||
|
||||
---
|
||||
### 状态与去重
|
||||
|
||||
`subpackage_state.json` 当前保存:
|
||||
|
||||
- `BuildId`
|
||||
- `DownloadedFiles` 的 Path 与 MD5
|
||||
- `DownloadedPackageIds`
|
||||
|
||||
Package 完整成功后才记录请求包和依赖包。查询 Package 是否完成时会同时检查当前清单、PackageId 状态、依赖包状态和所有文件 MD5。
|
||||
|
||||
去重分为三层:清单 Path 唯一、进行中任务按 `Path + MD5` 合并、本地文件和状态校验通过后复用。文件只有完整写入并通过 MD5 后才更新状态和进度计数。
|
||||
|
||||
## 验证重点
|
||||
|
||||
- PersistentData、StreamingAssets 和 CDN 的 Manifest 回退不会卡住主线程。
|
||||
- 本地文件使用正确加载策略,平台 URL 使用协程。
|
||||
- GC/主动卸载后能重新加载,loader 映射和内存统计正确。
|
||||
- V2 差异结果在下载前回调,失败信息包含 Path、期望 MD5 和尝试 URL。
|
||||
- 冷启动会从状态继续按文件校验,已完成文件不重复下载。
|
||||
- 已下载 Package 在远端 MD5、依赖或 FilePaths 变化后参与更新。
|
||||
|
||||
## 文档关联
|
||||
|
||||
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
|
||||
|
||||
| 文档 | 关联原因 |
|
||||
|---|---|
|
||||
| [modules/模块索引.md](../../模块索引.md) | 模块索引是项目知识总入口 |
|
||||
| [modules/frameworks/UI窗口系统/README.md](../UI窗口系统/README.md) | UI 资源加载依赖资源管理 |
|
||||
| [modules/frameworks/实体系统/README.md](../实体系统/README.md) | 实体资源加载依赖资源管理 |
|
||||
| [modules/frameworks/场景系统.md](../场景系统.md) | 场景资源加载依赖资源管理 |
|
||||
| [modules/frameworks/资源管理/资源加载重构方案.md](资源加载重构方案.md) | AssetBundle 加载链路重构方案 |
|
||||
| [rules/10-架构说明.md](../../../rules/10-架构说明.md) | 架构说明定义总体分层和依赖方向 |
|
||||
| [rules/30-模块登记规则.md](../../../rules/30-模块登记规则.md) | 模块登记规则定义分类和状态口径 |
|
||||
| [资源管理使用指南](使用指南.md) | 业务调用、卸载、内存和分包 API |
|
||||
| [模块索引](../../模块索引.md) | 模块入口和状态 |
|
||||
| [UI 窗口系统](../UI窗口系统/README.md) | UI Prefab 加载与卸载 |
|
||||
| [Hotfix 启动](../Hotfix启动.md) | Bootstrap 代码加载入口 |
|
||||
| [架构说明](../../../rules/10-架构说明.md) | ESF/HF 源码边界 |
|
||||
| [工作流清单](../../../rules/40-工作流清单.md) | 生成和平台验证流程 |
|
||||
|
||||
@@ -1,102 +1,174 @@
|
||||
# 资源管理使用指南
|
||||
|
||||
## 一、AssetBundle 资源加载
|
||||
## 一、加载资源
|
||||
|
||||
### 1.1 加载资源示例
|
||||
业务统一通过 `HFFunCatalogue.Asset` 调用:
|
||||
|
||||
```csharp
|
||||
public class ResourceLoader : MonoBehaviour
|
||||
HFFunCatalogue.Asset.LoadAsset(@"Perfabs\Tower\LavaGuard.prefab", (response, args) =>
|
||||
{
|
||||
public void LoadPlayerModel(int resId, Action<GameObject> onLoaded)
|
||||
GameObject prefab = response.GetAsset<GameObject>();
|
||||
if (prefab == null)
|
||||
{
|
||||
// 通过 HFFunCatalogue 统一入口加载资源
|
||||
// resId: 从 Res 配置表中读取的资源 ID
|
||||
HFFunCatalogue.Asset.LoadAsset(resId, (res, obj) =>
|
||||
{
|
||||
if (obj != null)
|
||||
{
|
||||
GameObject model = res.GetAsset<GameObject>();
|
||||
onLoaded?.Invoke(model);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError($"加载失败: resId={resId}");
|
||||
}
|
||||
});
|
||||
SFP.Error("Load LavaGuard failed.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 1.2 实体资源加载示例
|
||||
|
||||
```csharp
|
||||
// 在 Entity 中加载资源
|
||||
HFFunCatalogue.Asset.LoadAsset(data.EntityRes.Id, (res, obj) => {
|
||||
if (!entity.isDeath)
|
||||
{
|
||||
var Boddy = res.GetAsset<GameObject>();
|
||||
data.SetBoddy(Boddy);
|
||||
}
|
||||
GameObject instance = GameObject.Instantiate(prefab);
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
资源路径仍按项目 `HFAssetBundlePathAttribute`、语言目录和 AssetBundle 配置解析。业务不要直接创建 `HFAssetLoader` 或依赖 assetBundleKey。
|
||||
|
||||
## 二、资源管理 API
|
||||
## 二、主动卸载
|
||||
|
||||
### 2.1 常用方法
|
||||
业务使用易读名称接口:
|
||||
|
||||
| 类别 | 管理器 | 常用方法 |
|
||||
|-----|-------|---------|
|
||||
| **资源** | `HFABMgr` | `LoadAsset()`, `OnStartLoad()` |
|
||||
|
||||
### 2.2 资源加载流程
|
||||
|
||||
```
|
||||
资源加载流程:
|
||||
OnStartLoad(request)
|
||||
│
|
||||
▼
|
||||
检查缓存 → 获取/创建 HFAssetLoader
|
||||
│
|
||||
▼
|
||||
loader.AddRequest(request)
|
||||
│
|
||||
▼
|
||||
loader.LoadAsset() → 异步加载
|
||||
│
|
||||
▼
|
||||
加载完成 → 回调处理
|
||||
```csharp
|
||||
bool prefabUnloaded = HFFunCatalogue.Asset.UnloadPrefabObject("LavaGuard");
|
||||
bool assetUnloaded = HFFunCatalogue.Asset.UnloadAssetObject("common_icon");
|
||||
```
|
||||
|
||||
---
|
||||
调用前必须确认业务实例和窗口已经释放引用。框架会查找唯一 loader,并检查加载、请求和释放状态;未找到、重名或仍不可卸载时返回 `false`。
|
||||
|
||||
## 三、资源管理注意事项
|
||||
`AssetBundle.Unload(false)` 不会自动销毁已经实例化的 GameObject。业务仍应先关闭窗口或 Destroy 实例,再请求卸载 Bundle 内存。
|
||||
|
||||
### 3.1 性能优化建议
|
||||
同名资源存在多个 loader 时不要随机卸载;使用更完整的资源路径,或根据错误日志定位冲突来源。
|
||||
|
||||
- 及时释放不再使用的资源
|
||||
- 使用异步加载避免卡顿
|
||||
- 合理使用资源缓存
|
||||
## 三、内存查看
|
||||
|
||||
---
|
||||
```csharp
|
||||
long bytes = HFFunCatalogue.Asset.GetLoadedAssetBundleMemorySize();
|
||||
float kb = HFFunCatalogue.Asset.GetLoadedAssetBundleMemorySizeKB();
|
||||
float mb = HFFunCatalogue.Asset.GetLoadedAssetBundleMemorySizeMB();
|
||||
string text = HFFunCatalogue.Asset.GetLoadedAssetBundleMemorySizeText();
|
||||
HFFunCatalogue.Asset.LogLoadedAssetBundleMemorySize();
|
||||
```
|
||||
|
||||
**版本**: v1.0
|
||||
**生效日期**: 2026年
|
||||
**适用范围**: GiftGameX 项目业务开发人员
|
||||
该数值是已完成 loader 的 AssetBundle 内存估算。`LoadFromFileAsync` 没有托管 byte[] 时使用文件长度估算,因此只用于调试面板和趋势观察。
|
||||
|
||||
---
|
||||
## 四、AssetBundle CDN 保存路径
|
||||
|
||||
平台层可以在初始化时配置:
|
||||
|
||||
```csharp
|
||||
HFFunCatalogue.Asset.SetCdnSaveRootPath(platformSaveRoot);
|
||||
```
|
||||
|
||||
未配置或传入无效值时使用 PersistentData 资源根目录。平台专用目录由平台适配层传入,不在通用 ESF 逻辑中硬编码。
|
||||
|
||||
## 五、分包 V2 配置
|
||||
|
||||
非热更启动层使用:
|
||||
|
||||
```csharp
|
||||
SFSubPackageRuntimeV2.Instance.SetDownloadConfig(
|
||||
downloadSetting.WebBaseUrlBase,
|
||||
downloadSetting.AppVersion,
|
||||
downloadSetting.AppPartner);
|
||||
```
|
||||
|
||||
如平台使用自定义文件系统,调用 `SetLocalStorage` 重载传入 exists、read、write 和 delete 回调。默认存储根目录为 PersistentData。
|
||||
|
||||
旧 `SetDownLoadUri(string)` 只作为单地址兼容入口,新代码优先使用 `SetDownloadConfig`。
|
||||
|
||||
## 六、启动代码更新
|
||||
|
||||
`GameDllManager` 负责调用:
|
||||
|
||||
```csharp
|
||||
SFSubPackageRuntimeV2.Instance.CheckAndDownloadBootstrapFiles(
|
||||
OnProgress,
|
||||
OnBootstrapCompleted,
|
||||
OnManifestFailed);
|
||||
```
|
||||
|
||||
成功下载不等于启动完成。AOT、HotUpdate 和热更入口成功后,调用:
|
||||
|
||||
```csharp
|
||||
SFSubPackageRuntimeV2.Instance.CommitBootstrapCodeLoaded(OnCommitted);
|
||||
```
|
||||
|
||||
提交成功后普通资源 API 才可使用。失败时调用 `InvalidateBootstrapCode` 保持门禁关闭,并向业务展示重试或退出。
|
||||
|
||||
## 七、更新普通资源
|
||||
|
||||
### 更新基础、BuiltIn 和历史包
|
||||
|
||||
```csharp
|
||||
HFSubPackageManager.Instance.CheckAndDownloadAllFiles(
|
||||
progress => UpdateLoading(progress),
|
||||
() => OnResourceUpdateCompleted(),
|
||||
result => SFP.Error(SFDownloadResultLog.Format("普通资源下载失败", result)));
|
||||
```
|
||||
|
||||
该接口会先完成差异扫描并回调差异结果,再开始删除旧遮挡文件和下载。它只处理基础文件、BuiltIn 包、历史已下载包及依赖闭包。
|
||||
|
||||
### 按枚举下载 Package
|
||||
|
||||
```csharp
|
||||
HFSubPackageManager.Instance.CheckAndDownloadPackage(
|
||||
enHFSubPackageId.PACKAGE_1001,
|
||||
progress => UpdateLoading(progress),
|
||||
() => OnPackageReady(),
|
||||
result => SFP.Error(SFDownloadResultLog.Format("Package 下载失败", result)));
|
||||
```
|
||||
|
||||
枚举由编辑器生成,成员注释来自 Package DisplayName。不要手改 `Generated/enHFSubPackageId.cs`。
|
||||
|
||||
字符串接口仍保留:
|
||||
|
||||
```csharp
|
||||
HFSubPackageManager.Instance.CheckAndDownloadPackage(
|
||||
packageId, progress, finish, failed);
|
||||
```
|
||||
|
||||
### 查询 Package 状态
|
||||
|
||||
```csharp
|
||||
bool ready = HFSubPackageManager.Instance.IsPackageDownloaded(
|
||||
enHFSubPackageId.PACKAGE_1001);
|
||||
```
|
||||
|
||||
返回 `true` 需要:Bootstrap 已提交、Package 存在于当前清单和 `DownloadedPackageIds`、依赖包已记录、全部文件状态符合当前 MD5。`BuiltIn = true` 本身不等于业务已经下载过。
|
||||
|
||||
### 按文件兜底
|
||||
|
||||
资源加载知道目标 AB 路径时可以使用 `CheckAndDownloadFile`。运行时会从 `Files[]` 找到目标文件,递归展开 Dependencies,下载差异后重试原加载流程。
|
||||
|
||||
## 八、进度和失败
|
||||
|
||||
进度对象包含:
|
||||
|
||||
- `Stage`、`Max`、`DownloadedCount`
|
||||
- `TotalBytes`、`DownloadedBytes`
|
||||
- `CurrentPath`、`CurrentFileSize`
|
||||
- `CurrentFileDownloadedBytes`、`CurrentFileProgress`、`Progress`
|
||||
|
||||
失败结果包含:
|
||||
|
||||
- `DifferenceFiles`、`DeletedOldFiles`、`UpdatedFiles`
|
||||
- `FailedFiles[].Path`
|
||||
- `ExpectedMD5`、`Error`、`TriedUrls`
|
||||
|
||||
单文件只有写入完成且 MD5 通过后才增加完成计数。进程被结束后,下次启动会重新读取远端清单和本地状态,逐文件检查;已完成文件复用,未完成文件从文件头重新下载,不做单文件字节级续传。
|
||||
|
||||
## 九、验证清单
|
||||
|
||||
- PersistentData 文件正确时不访问 CDN。
|
||||
- PersistentData 错误但 StreamingAssets 正确时使用母包文件并处理遮挡旧文件。
|
||||
- 两处均无有效文件时从 CDN 下载到默认或外部保存路径。
|
||||
- 差异日志在普通资源下载进度之前输出。
|
||||
- 两个 Package 共享依赖时只建立一个 `Path + MD5` 任务。
|
||||
- 远端 MD5 变化后历史 Package 会参与 `CheckAndDownloadAllFiles`。
|
||||
- 下载失败释放进行中任务,下一次请求可以重试。
|
||||
- Android/WebGL 的 StreamingAssets 失败会继续 fallback,不会阻塞主线程。
|
||||
|
||||
## 文档关联
|
||||
|
||||
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
|
||||
|
||||
| 文档 | 关联原因 |
|
||||
|---|---|
|
||||
| [资源管理入口](README.md) | 本文件是资源管理模块的子文档 |
|
||||
| [UI 窗口系统](../UI窗口系统/README.md) | UI 资源加载依赖资源管理 |
|
||||
| [实体系统](../实体系统/README.md) | 实体资源加载依赖资源管理 |
|
||||
| [场景系统](../场景系统.md) | 场景资源加载依赖资源管理 |
|
||||
| [modules/模块索引.md](../../模块索引.md) | 模块索引是项目知识总入口 |
|
||||
| [rules/10-架构说明.md](../../../rules/10-架构说明.md) | 架构说明定义总体分层和依赖方向 |
|
||||
| [rules/30-模块登记规则.md](../../../rules/30-模块登记规则.md) | 模块登记规则定义分类和状态口径 |
|
||||
| [资源管理入口](README.md) | 架构、来源顺序和 V2 数据流 |
|
||||
| [UI 窗口系统](../UI窗口系统/README.md) | UI 加载与卸载调用方 |
|
||||
| [Hotfix 启动](../Hotfix启动.md) | Bootstrap 接入 |
|
||||
| [开发规则](../../../rules/20-开发规则.md) | 业务入口和异步规则 |
|
||||
|
||||
@@ -1,380 +0,0 @@
|
||||
# ESF_ 资源加载重构方案
|
||||
|
||||
## 一、目标与边界
|
||||
|
||||
本方案用于规划 StrayFog 框架中 AssetBundle 资源加载链路的重构。当前阶段先文档化设计,不直接修改代码。
|
||||
|
||||
重构范围只包含 `Assets/StrayFog/Editor/CopyToX/...` 下的 `ESF_` 框架源头代码。`ESF_` 后续会通过 CopyTo 流程同步到 `GameHFScripte` 目录,并替换为业务运行时使用的 `HF_` 命名。因此:
|
||||
|
||||
- 框架优化只修改 `ESF_` 源头。
|
||||
- 不直接修改 CopyTo 后的 `HF_` 副本代码。
|
||||
- 对外调用接口保持兼容,业务仍通过现有 `HF_` API 使用资源系统。
|
||||
|
||||
需要保持兼容的公开入口:
|
||||
|
||||
- `ESF_ABMgr.LoadInMemory(...)`
|
||||
- `ESF_ABMgr.SetModel(...)`
|
||||
- `ESF_ABMgr.SetDownloadWhilePlaying(...)`
|
||||
- `ESF_ABMgr.SetDownLoadUri(...)`
|
||||
- `ESF_ABMgr.RegisterDownloadMainifestFun(...)`
|
||||
- `ESF_AssetBundlePathAttribute` 现有构造与使用方式
|
||||
|
||||
计划新增的公开入口:
|
||||
|
||||
```csharp
|
||||
public bool TryUnloadPrefabInMemory(string _prefabName);
|
||||
public bool TryUnloadAssetInMemory(string _assetName);
|
||||
public long GetLoadedAssetBundleMemorySize();
|
||||
public void SetCdnSaveRootPath(string _saveRootPath);
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- `TryUnloadPrefabInMemory(string)` 供业务按预制体名称主动请求卸载,例如 `TryUnloadPrefabInMemory("GameLogicFightWindow")`。
|
||||
- `TryUnloadAssetInMemory(string)` 供业务按资源名称主动请求卸载,适用于非 prefab 资源。
|
||||
- 业务接口不暴露 `assetBundleKey`、`ESF_AssetBundlePathAttribute` 和 `_unloadAllLoadedObjects`,避免上层误用内部概念。
|
||||
- 内部卸载统一使用 `AssetBundle.Unload(false)`,只释放 Bundle 文件内存,不强制销毁已经实例化出去的对象。
|
||||
- 框架必须先判断资源已经没有框架侧活跃引用;如果仍在加载中、正在被窗口/任务使用、处于 ACTIVE 生命周期,或名称匹配不唯一,则拒绝卸载并返回 `false`。
|
||||
- `GetLoadedAssetBundleMemorySize()` 返回当前 ESF_ABMgr 统计到的已加载 AssetBundle 内存大小,单位为 byte,用于运行时监控面板或调试日志。
|
||||
- `SetCdnSaveRootPath(string)` 供外部按平台传入 CDN 热更新文件落盘根目录;如果未传入或传入空值,默认使用 PersistentData 资源根路径。
|
||||
|
||||
## 二、当前问题
|
||||
|
||||
### 2.1 职责封装需要加强
|
||||
|
||||
`ESF_ABMgr` 当前通过 partial 分布类承载 Manifest 读取、资源路径修正、Loader 管理、依赖查询、加载模式控制、GC 回收等职责。这种设计保留了统一入口,方便后续 CopyTo 和上层调用,是框架既有设计方向。
|
||||
|
||||
后续优化不应拆散 `ESF_ABMgr` 的统一入口,而是需要继续在 `ESF_ABMgr` 内部强化职责封装:把 Manifest 查询、加载来源选择、下载落盘、GC 回收、主动卸载等流程封装成清晰的内部方法或内部服务,减少方法之间直接互相修改状态。`ESF_AssetLoader` 内部也需要把依赖加载、主包加载、下载等待、进度计算和回调分发拆成更明确的状态步骤。
|
||||
|
||||
### 2.2 本地加载内存峰值偏高
|
||||
|
||||
PersistentData 等普通本地文件路径当前存在 `File.ReadAllBytes` + `AssetBundle.LoadFromMemoryAsync` 的加载方式。结合 Unity 官方说明和当前框架实现,这个判断成立,但需要精确理解:
|
||||
|
||||
- `File.ReadAllBytes(path)` 会先把整个 AssetBundle 文件读入托管 `byte[]`,这一步本身就会产生一份完整文件大小的内存占用。
|
||||
- `AssetBundle.LoadFromMemoryAsync(byte[])` 是从内存中的 byte[] 创建 AssetBundle,适合已经下载、解密或只能拿到 byte[] 的场景。
|
||||
- `AssetBundle.LoadFromFileAsync(path)` 是从磁盘文件异步加载 AssetBundle;Unity 官方说明它支持各种压缩类型,并且未压缩和 Chunk 压缩包可以直接从磁盘读取。
|
||||
- 当前框架构建 AssetBundle 时使用了 `BuildAssetBundleOptions.ChunkBasedCompression`,因此 PersistentData 下的普通本地文件优先改为 `LoadFromFileAsync` 是合理方向。
|
||||
|
||||
需要注意:如果资源包需要先经过 `LoadFileByDecryptFunc` 解密,或者来源是 StreamingAssets URL、CDN 下载数据、小游戏文件系统 byte[],仍然需要保留 `LoadFromMemoryAsync` 路径。
|
||||
|
||||
### 2.3 StreamingAssets 存在忙等阻塞
|
||||
|
||||
`ESF_AssetBundleHelper.OnReadStreamingAssets` 使用 `UnityWebRequest.SendWebRequest()` 后 `while (!async.isDone)` 空循环等待,会阻塞主线程。该问题在移动端和 WebGL/小游戏类环境中风险更高。
|
||||
|
||||
### 2.4 下载队列缺少统一并发上限
|
||||
|
||||
`ESF_DownLoadGroup` 的 `m_MaxTask` 由任务文件数量传入,可能导致一次任务内所有文件同时下载。资源数量较多时容易造成网络拥塞、内存峰值和下载回调竞争。
|
||||
|
||||
### 2.5 进度与回收存在边界风险
|
||||
|
||||
`ESF_ABMgr.progress` 在没有 active loader 时存在除零风险。GC 回收时销毁 loader GameObject 后,字典与内存统计需要同步清理,否则可能残留失效 loader 或重复统计。
|
||||
|
||||
## 三、目标架构
|
||||
|
||||
重构后仍保留 `ESF_ABMgr` 作为统一对外入口,并继续允许使用 partial 分布类组织代码;优化重点是把内部职责封装成更明确的方法组或内部服务。
|
||||
|
||||
| 模块 | 职责 |
|
||||
|-----|-----|
|
||||
| Manifest 管理封装 | 读取 Manifest、维护带 hash 包名映射、处理语言回退、提供依赖查询 |
|
||||
| Loader 管理封装 | 管理 assetBundleKey 到 loader 的映射、创建/复用/重载 loader |
|
||||
| Bundle 读取策略封装 | 根据加载模式选择 `LoadFromFileAsync`、`LoadFromMemoryAsync` 或下载数据加载 |
|
||||
| 下载队列封装 | 控制下载并发、失败重试、进度回调、下载完成数据回传 |
|
||||
| GC 缓存策略封装 | 维护 ACTIVE/Normal/LAZY 生命周期,回收时同步移除 loader 映射和内存统计 |
|
||||
| CDN 落盘路径策略封装 | 管理 CDN 下载后的保存根目录,支持外部传入平台专用路径,默认回退 PersistentData |
|
||||
|
||||
### 3.1 资源加载主流程
|
||||
|
||||
```
|
||||
LoadInMemory
|
||||
│
|
||||
▼
|
||||
Manifest 管理:确认 StreamingAssets / PersistentData / CDN 来源
|
||||
│
|
||||
▼
|
||||
Manifest 管理:按当前语言和各来源 Manifest 修正 ESF_AssetBundlePathAttribute
|
||||
│
|
||||
▼
|
||||
Loader 管理:创建或复用 ESF_AssetLoader
|
||||
│
|
||||
▼
|
||||
ESF_AssetLoader:加载依赖包
|
||||
│
|
||||
▼
|
||||
Bundle 读取策略:加载主 AssetBundle
|
||||
│
|
||||
▼
|
||||
ESF_AssetResponse:按原有回调返回资源
|
||||
```
|
||||
|
||||
### 3.2 本地加载策略
|
||||
|
||||
Unity 常用资源路径语义:
|
||||
|
||||
| 路径 | Unity 语义 | 常见平台表现 | 框架用途 |
|
||||
|-----|-----------|-------------|---------|
|
||||
| `Application.streamingAssetsPath` | 随包只读资源目录,构建时原样拷贝 `Assets/StreamingAssets` 内容 | Editor/Windows/Linux 多数是普通目录;macOS、iOS 有各自包内路径;Android 为 APK/JAR 内 URL;WebGL/Web 平台也是 URL 类路径 | 放母包内置 AssetBundle,作为首次启动和无热更文件时的基础资源 |
|
||||
| `Application.persistentDataPath` | 运行期可写、跨启动保留的数据目录 | iOS/Android 更新 App 后通常保留;Windows/macOS/Linux 为用户目录;WebGL/Web 为浏览器持久化文件系统;tvOS 不支持 | 放热更下载后的 AssetBundle,作为 StreamingAssets 缺失后的本地补充资源 |
|
||||
| `Application.dataPath` | 当前项目或 Player 数据目录 | 本框架只允许在 Editor 宏包裹的代码中使用;Editor 下指向项目 `Assets` | 仅用于 Editor 下路径推导和资源分析,不参与 Player 运行期加载策略 |
|
||||
|
||||
框架目标读取优先级:
|
||||
|
||||
```text
|
||||
非 CDN 模式:
|
||||
StreamingAssets Manifest
|
||||
│
|
||||
├─ Manifest 包含目标资源:从 StreamingAssets 加载随包资源
|
||||
│
|
||||
└─ Manifest 不包含目标资源
|
||||
│
|
||||
▼
|
||||
PersistentData Manifest
|
||||
│
|
||||
├─ Manifest 包含目标资源:从 PersistentData 加载热更资源
|
||||
│
|
||||
└─ Manifest 不包含目标资源
|
||||
│
|
||||
▼
|
||||
如果配置 CDN 路径,则从 CDN 下载
|
||||
|
||||
CDN 模式:
|
||||
CDN / 小游戏缓存 / 小游戏平台文件系统
|
||||
│
|
||||
└─ 不走 StreamingAssets / PersistentData fallback
|
||||
```
|
||||
|
||||
具体策略:
|
||||
|
||||
- 非 CDN 模式不直接按文件是否存在做第一判断,而是先读取 StreamingAssets 下的 Manifest。
|
||||
- 如果 StreamingAssets Manifest 包含目标资源,则从 `SFGlobalData.streamingAssetsRoot` 加载对应 AssetBundle。
|
||||
- 如果 StreamingAssets Manifest 不包含目标资源,再读取 PersistentData 下的 Manifest。
|
||||
- 如果 PersistentData Manifest 包含目标资源,则从 `SFGlobalData.assetBundleRoot` 加载对应 AssetBundle。
|
||||
- 如果两个本地 Manifest 都不包含目标资源,且配置了 CDN 路径,则从 CDN 下载目标资源;如果没有配置 CDN 路径,则返回明确失败。
|
||||
- StreamingAssets 是随包母包资源,PersistentData 是热更落盘资源;二者通过各自 Manifest 判断是否拥有目标资源。
|
||||
- PersistentData 命中且是普通文件系统路径时,优先使用 `AssetBundle.LoadFromFileAsync(path)`,避免 `File.ReadAllBytes + LoadFromMemoryAsync` 带来的额外 byte[] 内存峰值。
|
||||
- Android、WebGL/Web 等 StreamingAssets URL 类路径不能用普通 `File.Exists` / `File.ReadAllBytes` 判断和读取,需要通过 `UnityWebRequest` 异步读取。
|
||||
- StreamingAssets 读取到 byte[] 后仍使用 `AssetBundle.LoadFromMemoryAsync(data)`。
|
||||
- `Application.dataPath` 只允许出现在 `UNITY_EDITOR` 宏包裹的编辑器逻辑中,用于路径推导、AssetDatabase 资源定位或构建分析;运行期加载不能依赖该路径。
|
||||
- 显式 CDN 模式仍视为独立资源来源:优先走 CDN/小游戏缓存/小游戏文件系统逻辑,不参与 StreamingAssets 和 PersistentData 的本地 fallback。
|
||||
- 非 CDN 模式下的 CDN 下载只作为本地两个 Manifest 都不包含资源后的最后补充来源,前提是已经配置 CDN 地址。
|
||||
- CDN 下载返回的数据、小程序缓存读取返回的数据、需要 `LoadFileByDecryptFunc` 解密的数据,仍保持 byte[] 加载路径。
|
||||
- 后续如果发现部分平台对 `LoadFromFileAsync` 支持不一致,再增加内部策略开关;第一版不暴露为公开 API。
|
||||
|
||||
### 3.2.1 CDN 热更新落盘路径
|
||||
|
||||
CDN 下载属于热更新链路。下载成功后,需要把资源保存到当前平台可读写的位置,供后续启动或二次加载复用。
|
||||
|
||||
落盘根目录优先级:
|
||||
|
||||
```text
|
||||
外部传入 CDN 保存根目录
|
||||
│
|
||||
├─ 有效:使用外部路径
|
||||
│
|
||||
└─ 无效或未设置:使用 SFGlobalData.assetBundleRoot / PersistentData
|
||||
```
|
||||
|
||||
计划新增接口:
|
||||
|
||||
```csharp
|
||||
public void SetCdnSaveRootPath(string _saveRootPath);
|
||||
```
|
||||
|
||||
行为要求:
|
||||
|
||||
- 外部可在初始化资源系统时传入平台专用保存根目录。
|
||||
- 如果 `_saveRootPath` 为 `null`、空字符串或非法路径,则不覆盖默认值。
|
||||
- 未设置外部路径时,CDN 下载文件统一保存到 `SFGlobalData.assetBundleRoot`,也就是框架的 PersistentData 资源根路径。
|
||||
- 保存路径只影响 CDN 下载后的本地落盘位置,不改变 CDN 下载 URL。
|
||||
- 保存时仍保留 AssetBundle 的相对路径结构,避免不同 bundle 重名覆盖。
|
||||
- 小游戏平台如果有专用文件系统路径,应通过平台宏或外部初始化代码传入,不在通用逻辑里硬编码平台路径。
|
||||
- 后续读取 CDN 已下载文件时,优先从该保存根目录查找;未命中再走 CDN 下载。
|
||||
|
||||
平台隔离原则:
|
||||
|
||||
- 通用 ESF_ 逻辑只维护“外部传入路径优先,否则 PersistentData”的规则。
|
||||
- 平台差异通过宏定义包裹在平台适配层,例如小游戏、WebGL、原生移动端等各自传入保存路径。
|
||||
- 不在业务层暴露 `assetBundleKey` 或内部 Manifest 路径;业务只配置 CDN 地址和可选保存根目录。
|
||||
|
||||
### 3.3 StreamingAssets 异步读取
|
||||
|
||||
`OnReadStreamingAssets` 不再提供阻塞式读取给加载主链路使用。主链路应改为异步读取:
|
||||
|
||||
- 使用 `UnityWebRequest.Get`。
|
||||
- 通过协程或 loader 状态机等待完成。
|
||||
- 下载成功后使用 `AssetBundle.LoadFromMemoryAsync(data)`。
|
||||
- 下载失败时进入统一失败处理或 CDN fallback。
|
||||
|
||||
### 3.4 下载队列
|
||||
|
||||
下载管理器增加内部并发上限,建议默认值为 3。
|
||||
|
||||
要求:
|
||||
|
||||
- 单个 `ESF_DownLoadGroup` 不再以文件数量作为最大并发数。
|
||||
- 高优先级任务优先执行,但不能绕过全局并发上限。
|
||||
- 下载失败保持现有回调语义。
|
||||
- `WaitDownload` 回调保持兼容。
|
||||
|
||||
### 3.5 GC 与 loader 映射
|
||||
|
||||
GC 回收和主动卸载共用同一套内部释放逻辑,避免自动回收与业务手动卸载产生两套状态。
|
||||
|
||||
GC 自动回收时需要同时处理:
|
||||
|
||||
- `AssetBundle.Unload(false)`,默认不销毁已经实例化出去的对象。
|
||||
- Destroy loader GameObject。
|
||||
- 从 `mESF_AssetLoaderMaping` 移除对应 key。
|
||||
- 从生命周期池和内存统计中移除对应 key。
|
||||
|
||||
回收后再次加载同一资源时,必须能够重新创建 loader 并正常触发回调。
|
||||
|
||||
业务主动卸载接口:
|
||||
|
||||
```csharp
|
||||
public bool TryUnloadPrefabInMemory(string _prefabName);
|
||||
public bool TryUnloadAssetInMemory(string _assetName);
|
||||
```
|
||||
|
||||
行为要求:
|
||||
|
||||
- 根据 `_prefabName` 或 `_assetName` 在当前已加载 loader 中查找匹配资源,匹配字段以 `abPathAttr.assetName` 为准。
|
||||
- 找到唯一 loader,且 loader 已完成加载、没有排队请求、没有 active 任务、生命周期不为 ACTIVE 时,执行内部释放流程并返回 `true`。
|
||||
- 未找到 loader、名称匹配多个 loader、loader 未完成加载、资源正在使用、或资源已被释放时,返回 `false`,不抛异常。
|
||||
- 如果资源正在加载中,第一版不强制中断异步加载,避免破坏现有回调语义。
|
||||
- 卸载成功后,`mESF_AssetLoaderMaping`、生命周期池、内存统计必须同步移除对应 key。
|
||||
- 后续再次请求同一资源时,必须走正常创建 loader 和加载流程。
|
||||
- `AssetBundle.Unload(false)` 不会销毁已经实例化出去的 GameObject,但为了避免业务继续依赖原始 asset 对象,业务接口仍必须通过框架引用判断后才允许释放。
|
||||
|
||||
内部卸载能力:
|
||||
|
||||
```csharp
|
||||
bool OnUnloadInMemory(int _assetBundleKey);
|
||||
bool OnCanUnloadInMemory(ESF_AssetLoader _loader);
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- 按 key 卸载只作为框架内部实现和调试面板基础能力,不作为普通业务接口。
|
||||
- 所有卸载入口都必须经过 `OnCanUnloadInMemory` 安全判断。
|
||||
- 后续如果需要更精确的“完全没有引用”,应引入框架级引用计数或资源句柄释放机制;第一版以 loader 生命周期、请求队列和窗口/任务活跃状态作为安全判断依据。
|
||||
|
||||
内存查询接口:
|
||||
|
||||
```csharp
|
||||
public long GetLoadedAssetBundleMemorySize();
|
||||
```
|
||||
|
||||
行为要求:
|
||||
|
||||
- 返回当前 AssetBundle 加载器统计到的内存大小,单位为 byte。
|
||||
- 统计值以 loader 的 `Size` 为准;`LoadFromFileAsync` 无 byte[] 时,使用文件长度作为估算值。
|
||||
- 每次主动卸载或 GC 回收后同步扣减。
|
||||
- 只作为运行时查看和调试依据,不作为精确 Profiler 替代。
|
||||
|
||||
## 四、分批实施顺序
|
||||
|
||||
### 第一批:低风险修复
|
||||
|
||||
- 修正 `ESF_ABMgr.progress` 无 loader 时除零问题。
|
||||
- 新增 `TryUnloadPrefabInMemory(...)` 和 `TryUnloadAssetInMemory(...)` 业务主动卸载接口。
|
||||
- 新增 `GetLoadedAssetBundleMemorySize()` 内存统计查询接口。
|
||||
- 下载队列增加内部并发上限。
|
||||
- GC 回收时同步移除失效 loader。
|
||||
- 保持现有公开接口兼容。
|
||||
|
||||
### 第二批:加载策略重构
|
||||
|
||||
- 为 `ESF_AssetLoader` 引入更清晰的内部状态。
|
||||
- 非 CDN 模式读取优先级改为 `StreamingAssets Manifest -> PersistentData Manifest -> CDN fallback`。
|
||||
- Manifest 管理支持同时维护 StreamingAssets 与 PersistentData 两套本地 Manifest 查询结果。
|
||||
- CDN 下载支持外部传入保存根目录,未设置时默认保存到 PersistentData。
|
||||
- PersistentData 普通文件命中时优先使用 `AssetBundle.LoadFromFileAsync`。
|
||||
- 保留 StreamingAssets、CDN、小游戏缓存的 byte[] 加载路径。
|
||||
- 保持 `ESF_AssetResponse.GetAsset<T>()` 行为不变。
|
||||
|
||||
### 第三批:StreamingAssets 异步化
|
||||
|
||||
- 移除主链路中的忙等读取。
|
||||
- 将 StreamingAssets 读取接入 loader 状态机。
|
||||
- 统一成功、失败、fallback、进度回调。
|
||||
|
||||
### 第四批:内部封装整理
|
||||
|
||||
- 保留 `ESF_ABMgr` 统一入口和 partial 分布类组织方式。
|
||||
- 在不改变公开接口的前提下,将 Manifest、Loader 管理、Bundle 读取策略、下载队列、GC 策略封装成更清晰的内部方法组或内部服务。
|
||||
- 清理重复路径拼接和重复错误处理。
|
||||
|
||||
## 五、验证方案
|
||||
|
||||
验证以 Editor 菜单测试为主,建议新增或复用一个内部测试入口,用于覆盖以下场景:
|
||||
|
||||
| 场景 | 验证点 |
|
||||
|-----|-------|
|
||||
| Editor 模式加载 prefab | `LoadInMemory` 成功回调,`GetAsset<GameObject>()` 正常 |
|
||||
| PersistentData 本地 AB | 普通文件路径优先走 `LoadFromFileAsync` |
|
||||
| StreamingAssets 加载 | 不出现主线程忙等卡死 |
|
||||
| Manifest 缺失 | 返回清晰失败日志,不静默卡住 |
|
||||
| 资源缺失 | 触发 failed 回调,错误信息包含资源路径 |
|
||||
| 语言回退 | 当前语言缺资源时回退默认语言 |
|
||||
| CDN/边玩边下 | 触发 `WaitDownload`,下载完成后继续加载 |
|
||||
| SpriteAtlas 请求 | `SpriteAtlasManager.atlasRequested` 能正确加载图集 |
|
||||
| 多资源并发下载 | 并发数不超过内部上限 |
|
||||
| 非 CDN 读取优先级 | StreamingAssets Manifest 包含目标资源时不读取 PersistentData 资源 |
|
||||
| PersistentData fallback | StreamingAssets Manifest 不包含目标资源时,再查 PersistentData Manifest |
|
||||
| CDN fallback | 两个本地 Manifest 都不包含目标资源且配置 CDN 时,触发 CDN 下载 |
|
||||
| 无 CDN fallback | 两个本地 Manifest 都不包含目标资源且未配置 CDN 时,返回明确失败 |
|
||||
| CDN 模式路径隔离 | CDN 模式不走 StreamingAssets / PersistentData fallback |
|
||||
| CDN 默认落盘路径 | 未设置外部保存路径时,下载文件保存到 PersistentData 资源根目录 |
|
||||
| CDN 外部落盘路径 | 设置外部保存路径时,下载文件保存到外部路径并保留相对目录结构 |
|
||||
| 平台路径隔离 | 平台专用保存路径通过宏或初始化代码传入,通用逻辑不硬编码平台路径 |
|
||||
| GC 后重新加载 | 回收后能重新创建 loader 并成功加载 |
|
||||
| 主动卸载资源 | `TryUnloadPrefabInMemory` 返回成功,内存统计下降,再次加载正常 |
|
||||
| 正在使用资源卸载 | 资源处于 ACTIVE 或仍有框架侧引用时返回 `false`,游戏对象不受影响 |
|
||||
| 重名资源卸载 | 多个已加载资源同名时返回 `false`,要求后续使用更明确名称或内部诊断工具处理 |
|
||||
| 查询加载内存 | `GetLoadedAssetBundleMemorySize` 返回 byte 数,加载/卸载后变化合理 |
|
||||
|
||||
## 六、风险点
|
||||
|
||||
- `LoadFileByDecryptFunc` 依赖 byte[],如果某些本地包需要解密,不能直接走 `LoadFromFileAsync`。
|
||||
- StreamingAssets 在不同平台路径格式不同,异步读取必须保留原有平台兼容性。
|
||||
- 小游戏环境使用 `MiniGame` 抽象,不能直接套用普通文件系统逻辑。
|
||||
- GC 逻辑改动会影响资源复用,需要重点验证窗口、图集、材质、文本配置等常驻资源。
|
||||
- CopyTo 后 `ESF_` 会替换为 `HF_`,新增内部类命名必须遵守替换规则,避免生成后引用不一致。
|
||||
|
||||
## 七、Unity 路径参考
|
||||
|
||||
- Unity Manual - Streaming Assets: `https://docs.unity3d.com/2022.3/Documentation/Manual/StreamingAssets.html`
|
||||
- Unity API - `Application.streamingAssetsPath`: `https://docs.unity3d.com/2022.3/Documentation/ScriptReference/Application-streamingAssetsPath.html`
|
||||
- Unity API - `Application.persistentDataPath`: `https://docs.unity3d.com/2022.3/Documentation/ScriptReference/Application-persistentDataPath.html`
|
||||
- Unity API - `Application.dataPath`: `https://docs.unity3d.com/2022.3/Documentation/ScriptReference/Application-dataPath.html`
|
||||
- Unity API - `AssetBundle.LoadFromMemoryAsync`: `https://docs.unity3d.com/2022.3/Documentation/ScriptReference/AssetBundle.LoadFromMemoryAsync.html`
|
||||
- Unity API - `AssetBundle.LoadFromFileAsync`: `https://docs.unity3d.com/2022.3/Documentation/ScriptReference/AssetBundle.LoadFromFileAsync.html`
|
||||
- Unity API - `BuildAssetBundleOptions.ChunkBasedCompression`: `https://docs.unity3d.com/2022.3/Documentation/ScriptReference/BuildAssetBundleOptions.ChunkBasedCompression.html`
|
||||
|
||||
## 八、默认决策
|
||||
|
||||
- 第一阶段只文档化方案,不改 ESF_ 代码。
|
||||
- 后续代码实现只改 `ESF_` 源头,不直接改 `HF_` 副本。
|
||||
- 公开接口保持兼容。
|
||||
- 允许新增 `TryUnloadPrefabInMemory(...)`、`TryUnloadAssetInMemory(...)` 和 `GetLoadedAssetBundleMemorySize()` 作为可选公开能力。
|
||||
- 允许新增 `SetCdnSaveRootPath(...)` 作为 CDN 热更新落盘根目录配置入口。
|
||||
- 允许新增内部 `ESF_` 类和内部辅助方法。
|
||||
- 非 CDN 模式默认按 Manifest 查找顺序读取:StreamingAssets Manifest、PersistentData Manifest、CDN fallback。
|
||||
- PersistentData 普通文件默认优先 `LoadFromFileAsync`。
|
||||
- StreamingAssets、CDN、小游戏路径保持内存加载。
|
||||
- 下载证书校验暂不作为第一批主目标,后续单独处理。
|
||||
- CDN 下载保存根目录允许外部传入;未传入时默认使用 PersistentData 资源根目录。
|
||||
|
||||
---
|
||||
|
||||
## 文档关联
|
||||
|
||||
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
|
||||
|
||||
| 文档 | 关联原因 |
|
||||
|---|---|
|
||||
| [资源管理入口](README.md) | 本文件是资源管理模块的子文档 |
|
||||
| [资源管理使用指南](使用指南.md) | 当前资源加载使用指南 |
|
||||
| [modules/模块索引.md](../../模块索引.md) | 模块索引是项目知识总入口 |
|
||||
| [rules/10-架构说明.md](../../../rules/10-架构说明.md) | 架构说明定义总体分层和依赖方向 |
|
||||
| [rules/30-模块登记规则.md](../../../rules/30-模块登记规则.md) | 模块登记规则定义分类和状态口径 |
|
||||
Reference in New Issue
Block a user