37 KiB
Unity式 Library 资产导入与缓存系统重构方案
0. 当前实施进度(2026-04-02)
本节用于记录方案的实际落地状态,避免文档只停留在设计层。
0.1 阶段进度表
| 阶段 | 状态 | 当前结果 |
|---|---|---|
| 阶段 0:基础类型与工程边界收口 | 已完成 | 已引入 AssetGUID、AssetRef,开始把运行时 ResourceGUID 与编辑器资产身份拆开。 |
阶段 1:.meta 与 SourceAssetDB |
已完成 | 已支持自动生成 .meta、扫描 Assets、建立 guid/path/importer 索引,并支持路径到 GUID/AssetRef 的反查。 |
| 阶段 2:Artifact Store 与 ArtifactDB | 已完成 | 已落地 Library/SourceAssetDB、Library/ArtifactDB、Library/Artifacts,支持 artifact key、导入失效判断与产物目录管理。 |
| 阶段 3:TextureImporter | 已完成 | 已支持纹理导入为 xctex,运行时可直接从 artifact 读取,不再总是解码原始图片。 |
| 阶段 4:ModelImporter | 已完成(初版) | 已支持模型导入为 xcmesh,并缓存模型关联纹理的 xctex;ResourceManager 会优先走 artifact。 |
| 阶段 5:Material 系统 lazy 引用化 | 部分完成 | 已补齐材质序列化所需的 tag/property/texture binding 访问接口,但仍是 eager 贴图加载,未完成真正的延迟纹理解析。 |
| 阶段 6:Scene / Component 引用迁移 | 部分完成 | MeshFilterComponent 与 MeshRendererComponent 已开始双写路径和 AssetRef,可兼容旧格式读取。Scene 全量格式迁移尚未完成。 |
| 阶段 7:异步导入与异步加载 | 未开始 | 当前导入与大资源加载仍发生在主线程,是仍会卡 editor 的直接原因。 |
| 阶段 8:清理、GC、工具与可视化 | 未开始 | 还没有 Reimport All、依赖图查看、orphan artifact 清理、导入面板等工具。 |
0.2 本轮已经落地的具体内容
- 新增
AssetGUID、AssetRef、ArtifactFormats、AssetDatabase,正式建立 Unity 式资产身份与导入缓存基础设施。 ResourceManager已接入AssetDatabase,资源加载时会先尝试EnsureArtifact(...),命中后走Library产物。TextureLoader已支持xctex,MeshLoader已支持xcmesh,从而把“导入”和“运行时加载”从代码路径上拆开。ProjectManager已支持忽略.meta文件显示,并在资源删除、移动、重命名时携带.metasidecar。MeshFilterComponent与MeshRendererComponent已开始从纯路径引用迁到AssetRef,目前是“旧路径兼容 + 新引用双写”模式。- 已为纹理 artifact、模型 artifact、组件
AssetRef序列化补充回归测试。
0.3 已验证结果
Debug下texture_tests.exe已通过,覆盖.meta自动生成、Library产物生成、二次EnsureArtifact不重复导入、按AssetRef再加载纹理。Debug下components_tests.exe已通过,覆盖MeshRendererComponent的AssetRef双写序列化/反序列化,以及项目内材质按AssetRef恢复。Release下mesh_tests.exe已通过,覆盖obj + mtl + 贴图导入为xcmesh + xctex、二次导入命中缓存、按AssetRef再加载模型。Release下XCEditor已构建通过。
0.4 当前版本仍然存在的限制
- 首次打开未缓存或失效的
obj资源时,仍会在主线程同步执行导入,editor 会明显卡住。 - 即使已经命中 artifact,当前模型 artifact 读取后仍会立即把关联贴图一起加载到内存,尚未做到材质纹理 lazy load。
- 首帧渲染阶段仍会发生 GPU 资源创建与上传,这部分也会带来一次性卡顿。
MaterialImporter还没有形成完整的独立 artifact 格式,材质系统仍处于过渡状态。- Scene 级别仍是“组件局部迁移”,还不是一套完整的新序列化协议。
1. 文档目的
本文档用于给 XCEngine 当前资源系统做一次正式的、可实施的重构设计,目标是把当前“打开场景时直接同步读取原始资源”的模式,重构为一套接近 Unity 的:
Assets作为源资源目录.meta作为稳定资产身份与导入配置Library作为导入产物与索引缓存- 场景、组件、材质、渲染运行时全部通过稳定资产引用访问缓存产物
本文档不是一句“加个缓存”式的小修,而是资产身份、导入、缓存、运行时加载、组件引用、编辑器流程的系统级重构方案。
2. 这次重构要解决什么问题
2.1 当前最直接的问题
当前 editor 打开一个引用了 obj 模型的 scene 时,慢的不是 scene 文件本身,而是 scene 打开过程中同步触发了原始资源导入。
以当前 project/Assets/Scenes/Backpack.xc 为例,scene 文件本身很小,但里面的 MeshFilter 直接引用了:
Assets/Models/backpack/backpack.obj
当前调用链是:
- SceneManager 打开 scene
- Scene 反序列化 GameObject 与 Component
- MeshFilterComponent 反序列化时,立刻按路径同步
Load<Mesh>() - MeshLoader 直接用 Assimp 读取原始
obj - MeshLoader 顺手把模型材质和贴图一起导入
- TextureLoader 直接读取并解码原始
png/jpg - 首帧渲染再把 CPU 资源上传到 GPU
这意味着当前“打开 scene”其实包含了“首次导入资源”。
2.2 当前系统的结构性问题
当前系统慢,不是某一个函数写得慢,而是系统边界本身就不对。问题包括:
- 资产身份不稳定。当前
ResourceGUID本质上是路径 hash,不是独立资产 ID。文件改名、移动、目录调整都会导致身份变化。 - Scene/Component 引用层错误。当前 scene 和组件直接存路径字符串,而不是稳定资产引用。
- 资源导入和运行时加载混在一起。
IResourceLoader现在既承担“从源文件导入”,又承担“运行时拿到资源对象”。 - 没有持久化导入缓存。当前没有等价于 Unity
Library/Artifacts的正式产物目录。 - 没有源资源数据库。当前没有等价于 Unity
SourceAssetDB的资产身份与路径索引层。 - 没有 artifact 数据库。当前没有等价于 Unity
ArtifactDB的导入结果索引层。 - 没有依赖图。模型依赖哪些贴图、材质、侧车文件,目前没有正式记录。
- 组件反序列化会立即加载资源。Scene 打开直接卡在主线程。
- 异步加载器目前基本是空壳,不能承担真正的后台导入与后台加载。
- 渲染时还有 editor 额外开销,例如 Scene View object-id pass 的首帧资源上传,但这属于次要问题。
2.3 为什么 Unity 看起来没这么卡
Unity 在打开 scene 时,通常不是重新解析 obj/png/jpg 源文件,而是:
- 通过
.meta的guid识别资产 - 通过
Library中已经存在的导入产物恢复资源 - 只有源文件、导入设置、依赖变化时,才重新导入
也就是说:
- Unity 把“导入”提前做了
- 你当前的 editor 把“导入”塞进了“打开场景”
这就是 Unity 里大场景不一定很卡,而你这里小 scene 也会卡的根本原因。
3. 本地 Unity 项目观察结果
本次方案参考了本地 Unity 项目:
D:\Xuanchi\Main\Editor
从该项目可以明确观察到以下结构:
3.1 .meta 文件
Unity 的每个源资源旁边都有 .meta 文件,最核心字段是:
guid- importer 类型
- importer 设置
例如 backpack.obj.meta 中可以看到:
guidModelImporter- 材质导入模式
- mesh 导入配置
- tangent/normal 配置
这说明 Unity 的源资源身份和导入配置是放在源资源旁的,而不是临时内存状态。
3.2 Library 的职责分层
在本地 Unity 项目里,Library 下可以看到:
SourceAssetDBArtifactDBArtifactsShaderCacheScriptAssembliesStateCache
这说明 Unity 的 Library 不是一个单独的缓存目录,而是多个子系统缓存的集合。
3.3 本次只模仿哪一部分
本次 XCEngine 初版只模仿 Unity 的“资产导入与缓存”这一部分,不复制以下内容:
- 脚本编译缓存
- 包管理缓存
- build 缓存
- editor 状态缓存
- shader 编译缓存
本次重点只放在:
Assets.metaSourceAssetDBArtifactDBArtifacts
4. 重构目标
4.1 总目标
把当前资源系统重构为:
源文件系统、资产身份系统、导入系统、artifact 缓存系统、运行时资源加载系统、场景资产引用系统
六层明确分离的正式资产管线。
4.2 直接目标
- 打开 scene 时不再直接读取原始
obj/png/jpg - 首次导入后,后续打开 scene 优先命中
Libraryartifact - 文件改名/移动后,scene 引用不失效
- 模型、材质、贴图引用不再依赖路径字符串
- 模型导入不再把所有外部贴图同步塞进
Mesh对象里 - 资源导入的失效条件可控、可追踪、可重建
4.3 长期目标
- 为后续 prefab、动画、shader、音频、打包系统留出统一入口
- 为后续后台导入、导入进度 UI、资产依赖分析、项目迁移工具提供基础设施
- 为后续 player 构建系统提供统一的“已导入资源来源”
5. 非目标
这次重构的第一版不追求:
- 100% 复制 Unity 内部数据库格式
- 立即实现所有资源类型的 importer
- 一步到位做完脚本编译缓存与 ShaderCache
- 一次性改完整个 editor 的所有资产面板功能
- 一开始就做压缩纹理格式转码、平台分包、远程缓存
第一版必须先把“资产导入缓存”这条主链路做对。
6. 设计原则
6.1 资产身份必须独立于路径
路径只是定位信息,不应该承担身份。
新的系统必须引入独立 AssetGUID,并把它落盘到 .meta。改名和移动只影响路径索引,不影响资产身份。
6.2 导入和运行时加载必须分离
导入器负责:
- 读取原始源文件
- 生成规范化中间产物
- 记录依赖
- 生成 artifact
运行时加载器负责:
- 读取 artifact
- 创建运行时资源对象
- 加入内存 cache
不能再让一个 Load(path) 同时承担两件事。
6.3 Scene 反序列化不能立即触发重型资源导入
Scene 打开时应该只恢复引用关系和组件数据,不应在反序列化过程中同步读取大模型和大贴图源文件。
6.4 运行时资源对象不能再“顺手拥有所有关联资源”
当前最大的结构性浪费之一是:
Mesh导入时直接把材质和贴图都挂进去Material运行时对象也直接持有纹理 handle
这样会导致:
- 只需要 mesh 时,材质和贴图也跟着进来
- 只渲染 base color 时,normal/specular/roughness/ao 也同步加载
重构后应改成:
Mesh只管 mesh 数据和默认材质引用Material只管材质属性和纹理引用- 纹理运行时加载按需触发
6.5 缓存键必须可重复计算
artifact 是否有效,必须由可重复计算的输入决定,而不是靠“我觉得没变”。
artifact key 必须包含:
- 源文件内容 hash
- 导入设置 hash
- importer version
- 依赖文件/依赖资产 hash
- 平台或目标格式
7. 新系统的核心概念
7.1 AssetGUID
新增稳定资产 ID。
定义建议:
- 128-bit
- 文本形式为 32 位小写十六进制
- 在
.meta首次生成时随机创建 - 之后不再因为路径变化而变化
注意:
- 当前
ResourceGUID不是这个概念 - 当前
ResourceGUID可以暂时保留给运行时 cache 用 AssetGUID用于编辑器和资产引用层
7.2 LocalID
一个源资产导入后,可能产生多个子资源。
例如一个 obj 模型可能产生:
- 主模型资产
- 一个或多个
Mesh - 一个或多个默认
Material - 内嵌纹理子资产
因此需要 LocalID 表示“某个资产内部的某个子对象”。
定义建议:
- 64-bit
- 在 importer 内根据“子资源逻辑路径”稳定生成
- 不依赖内存地址
- 尽量在重导入后保持稳定
7.3 AssetRef
新的统一资产引用结构:
AssetRef = { assetGuid, localID, resourceType }
所有 scene、prefab、material、component 最终都应该依赖这个结构,而不是路径字符串。
7.4 SourceAssetDB
源资产数据库,用来记录:
AssetGUID -> 源路径源路径 -> AssetGUID.meta内容- importer 类型
- importer 设置 hash
- 源文件指纹
- 是否为文件/文件夹
- 上次导入状态
7.5 ArtifactKey
导入产物的内容键。
它不是资产 GUID,而是本次“导入结果”的键。
同一个 AssetGUID 在不同 importer 设置或不同依赖版本下,会产生不同 ArtifactKey。
7.6 ArtifactDB
导入结果数据库,用来记录:
(AssetGUID, importer signature) -> ArtifactKeyArtifactKey -> 产物清单ArtifactKey -> 依赖集合- 主子资源映射
- sub-asset
LocalID -> artifact object
7.7 Artifact Store
真正落盘的导入产物目录,对应 Unity 的 Library/Artifacts。
7.8 Importer
新的导入器不再等于运行时 loader,而是只负责:
- 从源资源生成 artifact
- 声明依赖
- 产出 sub-asset 映射
7.9 Runtime Artifact Loader
新的运行时资源加载器负责:
- 从 artifact 读取
xcmesh/xctex/xcmat/... - 创建
Mesh/Texture/Material等运行时对象 - 写入
ResourceManager
8. 总体架构
8.1 目标数据流
Assets + .meta
->
Project Asset Scan
->
SourceAssetDB
->
Importer Registry
->
ArtifactDB + Artifact Store
->
Runtime Artifact Loader
->
ResourceManager
->
Scene / Component / Renderer
8.2 目标职责边界
源文件层
只负责:
Assets下的真实源文件- 同路径
.meta
编辑器资产数据库层
只负责:
- 维护
AssetGUID - 管理路径与 meta
- 决定是否需要 reimport
- 维护依赖索引
导入层
只负责:
- 从源文件读取
- 生成 artifact
- 写入
Library
运行时资源层
只负责:
- 读取 artifact
- 构造运行时资源对象
- 做内存 cache
Scene/Component 层
只负责:
- 保存
AssetRef - 请求资源
- 不直接操作源文件
9. 推荐的目录结构
建议项目目录最终如下:
project/
├── Assets/
│ ├── Scenes/
│ ├── Models/
│ ├── Textures/
│ ├── Materials/
│ └── ... + 对应 .meta
├── Library/
│ ├── SourceAssetDB/
│ │ ├── assets.json
│ │ ├── guid_index.json
│ │ ├── path_index.json
│ │ └── folders.json
│ ├── ArtifactDB/
│ │ ├── artifacts.json
│ │ ├── asset_to_artifact.json
│ │ ├── dependency_index.json
│ │ └── subasset_index.json
│ ├── Artifacts/
│ │ ├── 00/
│ │ ├── 01/
│ │ ├── ...
│ │ └── ff/
│ ├── ImportLogs/
│ └── CacheVersion.json
├── .xceditor/
│ └── imgui_layout.ini
└── Project.xcproject
9.1 为什么初版不直接做成 Unity 那种单文件 DB
Unity 的 SourceAssetDB 与 ArtifactDB 在本地看起来更像单文件数据库。
但 XCEngine 初版建议先做成:
- 同名目录
- 多个版本化 JSON 索引文件
理由:
- 易实现
- 易调试
- 易人工修复
- 易写测试
- 后续可以在不改外部结构的前提下替换为二进制 DB 或 SQLite
也就是说:
- 逻辑结构模仿 Unity
- 存储后端先选工程上可落地的版本
10. .meta 系统设计
10.1 总体目标
每个 Assets 内的文件和文件夹都应该有 .meta。
.meta 要承担:
- 稳定
AssetGUID - importer 类型
- importer 设置
- importer 版本
- 用户数据与扩展字段
10.2 初版格式建议
为了最大程度贴近 Unity,推荐采用“Unity 风格文本 + 严格子集解析器”的方式,而不是纯 JSON。
示例:
fileFormatVersion: 1
guid: 07215647f41c2504abc024d70182ba63
folderAsset: false
importer: ModelImporter
importerVersion: 1
userData:
labels: []
ModelImporter:
materials:
importMode: ImportAsSubAssets
searchMode: LocalDirectory
meshes:
globalScale: 1.0
generateNormals: FromSource
generateTangents: IfNeeded
importCameras: false
importLights: false
weldVertices: true
10.3 为什么不直接上完整 YAML
完整 YAML 解析超出当前工程必要范围。
初版建议:
- 文本表现模仿 Unity
- 解析器只支持我们定义的固定字段子集
- 统一由 editor 写回,尽量不手写任意结构
10.4 .meta 的生成规则
Assets下扫描到新文件/文件夹时,若缺少.meta,自动生成- 新生成
.meta时写入随机AssetGUID - 重复 GUID 检测到时,自动再生并记录错误日志
- 文件重命名、移动时
.meta跟随文件一起移动 - 文件夹同样有
.meta
11. 资产数据库设计
11.1 SourceAssetDB 记录结构
建议的 SourceAssetRecord:
guid
relativePath
metaPath
isFolder
importerName
importerVersion
metaHash
sourceHash
sourceFileSize
sourceWriteTime
importSettingsHash
lastKnownArtifactKey
state
其中:
sourceHash用于真正判断内容是否变化sourceFileSize + sourceWriteTime用作快速短路metaHash用于判断 importer 设置变化
11.2 ArtifactDB 记录结构
建议的 ArtifactRecord:
artifactKey
assetGuid
importerName
importerVersion
targetProfile
inputSignatureHash
artifactPath
mainObjectLocalID
subAssets[]
dependencies[]
createdAt
11.3 依赖记录结构
依赖应支持两种:
- 源文件依赖
- 资产依赖
建议字段:
dependencyKind = SourceFile | Asset
sourcePath / assetGuid
expectedHash
12. 哈希与键设计
12.1 AssetGUID
用途:
- 标识“这个资产是谁”
生成方式:
- 首次生成时随机 UUID 风格 128-bit
12.2 ContentHash
用途:
- 标识“这个文件内容是什么”
建议实现:
- 引入
xxHash的XXH3_128 - 用于源文件内容 hash、meta 文本 hash、artifact 输入签名 hash
12.3 ArtifactKey
用途:
- 标识“这次导入结果是什么”
建议组成:
ArtifactKey = Hash128(
importerName,
importerVersion,
assetGuid,
sourceContentHash,
importSettingsHash,
dependencySignatureHash,
targetProfile,
artifactSchemaVersion
)
12.4 Runtime Resource GUID
当前 ResourceGUID 不应再直接由源路径生成。
重构后建议:
ResourceGUID用于运行时对象 cache- 由
artifactKey + localID + resourceType生成
这样:
- 资产改名不会改变运行时对象身份
- 同一 artifact 的 sub-asset 可以稳定寻址
13. Artifact Store 设计
13.1 落盘组织方式
建议采用哈希分片目录:
Library/Artifacts/07/07ab23cd.../
目录名是完整 artifactKey,前两位做分片。
13.2 每个 artifact 包内的内容
建议不要做成一个不可读 blob,而是一个小型包目录:
Library/Artifacts/07/07ab23cd.../
├── artifact.json
├── main.xcasset
├── mesh_1001.xcmesh
├── material_2001.xcmat
├── material_2002.xcmat
└── embedded_texture_3001.xctex
这样做的原因:
- 易调试
- 易增量扩展
- 易人工清理
- 适合初版实现
13.3 artifact.json 内容
建议至少包含:
artifactKeyassetGuidimporterimporterVersionschemaVersionmainObjectsubAssetsdependenciesproducedFiles
14. Importer 体系重构
14.1 现有问题
当前 MeshLoader、TextureLoader 直接从源文件创建运行时对象。
这导致:
- 每次首次访问都重复导入
- 无法形成 Library artifact
- 无法精确 reimport
14.2 新的职责拆分
建议把当前体系拆成两类接口:
IAssetImporter
负责:
- 从源文件导入到 artifact
- 生成 sub-asset
- 记录依赖
IArtifactLoader
负责:
- 从 artifact 还原为运行时资源对象
14.3 推荐接口草案
IAssetImporter
- GetImporterName()
- GetImporterVersion()
- CanImport(sourcePath, meta)
- Import(request, output)
- GatherDependencies(request, outDeps)
- BuildDefaultMeta()
IArtifactLoader
- GetResourceType()
- CanLoad(artifactObject)
- LoadFromArtifact(artifactObject, loadContext)
14.4 对现有类的映射
建议演进为:
TextureLoader拆为TextureImporter + TextureArtifactLoaderMeshLoader拆为ModelImporter + MeshArtifactLoaderMaterialLoader拆为MaterialImporter + MaterialArtifactLoader
15. 各资产类型的初版实现细节
15.1 Texture 资产
15.1.1 输入
.png.jpg.jpeg- 后续再扩展其它格式
15.1.2 输出
建议输出:
xctex二进制文件- 可选
artifact.json
15.1.3 初版 xctex 内容
- magic
- schemaVersion
- textureFormat
- width
- height
- mipCount
- colorSpace
- importFlags
- payloadOffset
- payloadSize
- pixel/compressed data
15.1.4 初版策略
初版先不做 GPU 压缩纹理缓存,先做:
- RGBA8_UNORM
- 可选 sRGB 标记
- 可选 mipmap 预生成
先把“只导一次、只读 artifact”做对,再谈 BC 压缩。
15.2 Material 资产
15.2.1 当前问题
当前 Material 运行时对象直接持有纹理 handle,容易在加载材质时把所有纹理一并拉进来。
15.2.2 重构目标
Material artifact 应该存:
- shader 标识
- render state
- float/int/vector 属性
- 纹理槽位对应的
AssetRef<Texture>
而不是直接保存已加载的纹理对象。
15.2.3 运行时行为
运行时 Material 应支持:
- 保存纹理引用
- 延迟请求实际
Texture - 由渲染管线按需解析纹理
这样可以避免打开 scene 时把所有材质贴图全同步加载。
15.3 Model 资产
15.3.1 当前问题
当前 MeshLoader 会:
- 读取
obj - 生成 mesh
- 导入材质
- 导入贴图
- 把材质和贴图一起挂到
Mesh
这会造成过度加载。
15.3.2 新设计
ModelImporter 应该把模型导入成一个 artifact 包,内部可包含多个 sub-asset:
- 主模型对象
- 一个或多个
Meshsub-asset - 零个或多个默认
Materialsub-asset - 零个或多个内嵌纹理 sub-asset
外部贴图不应再被复制进入 mesh runtime 对象。
15.3.3 外部贴图处理
如果 obj/mtl 引用的是外部贴图文件:
- 先通过相对路径解析到
Assets内真实资源 - 找到该贴图的
AssetGUID - 在导出的
Materialsub-asset 中写入AssetRef<Texture> - 不在 model artifact 中嵌入外部贴图像素数据
只有模型文件内嵌的纹理,才作为 model 的 sub-asset 输出。
15.3.4 xcmesh 内容建议
- header
- vertex buffer layout
- vertex count
- index count
- bounds
- sections
- 默认材质
AssetRef - vertex data blob
- index data blob
15.3.5 mesh runtime 对象要求
新的 Mesh 运行时对象应该:
- 持有顶点、索引、section、bounds
- 可选保存默认材质引用
- 不再持有
Texture* - 不再默认拥有一堆已创建好的
Material*
15.4 Scene 资产
15.4.1 这次不把 Scene 打成必须的二进制 artifact
初版不要求 scene 一定导成二进制 artifact。
原因:
- 当前性能瓶颈不在 scene 文本解析
- 瓶颈在 scene 打开时触发原始模型和贴图导入
所以初版 scene 可以继续保留 .xc 文本格式,但必须改引用方式。
15.4.2 Scene 引用重构
scene 中不应再保存:
mesh=Assets/Models/backpack/backpack.obj
而应改成:
meshGuid=<guid>meshLocalID=<id>
或者后续再统一为通用 AssetRef 文本格式。
15.4.3 兼容期
初版建议保留一段兼容窗口:
- 读取优先使用
guid + localID - 如果没有,再回退读取旧路径字段
- 迁移完成后,写出只写新格式
16. 组件层重构要求
16.1 MeshFilterComponent
当前问题:
- 反序列化时立刻同步
Load<Mesh>()
新要求:
- 反序列化只恢复
AssetRef<Mesh> - 不立即导入或加载
GetMesh()可做 lazy resolve- editor 可根据需要同步或异步请求资源
16.2 MeshRendererComponent
当前问题:
- 材质路径字符串 + 反序列化时直接同步加载
新要求:
- 保存
AssetRef<Material>列表 - 反序列化只恢复引用,不立即创建资源
- 渲染时按需 resolve
16.3 为什么这一步必须做
如果不改组件层,即使有 Library:
- scene 打开时仍会同步加载所有资源
- 只是“从 artifact 同步加载”,但主线程仍会卡
所以这次重构不只是 cache 落盘,还必须把组件反序列化改成纯数据恢复。
17. Runtime 资源加载重构
17.1 ResourceManager 的新职责
重构后 ResourceManager 应只负责:
- 运行时对象 cache
- artifact object 到 runtime object 的映射
- 引用计数
- 卸载策略
不再直接负责:
- 解析
.obj - 解码
.png/.jpg - 决定 asset 是否需要 reimport
17.2 AssetDatabase 与 ResourceManager 的关系
建议关系:
AssetDatabase负责编辑器世界的资产身份与 artifact 定位ResourceManager负责运行时对象生命周期
流程:
- Component 持有
AssetRef - 请求资源时交给
AssetDatabase AssetDatabase找到当前有效 artifact objectResourceManager用IArtifactLoader加载 artifact- 缓存 runtime object
17.3 渲染时的按需纹理解析
例如 forward pipeline 只需要 baseColorTexture 时:
- 只解析该槽位对应的
AssetRef<Texture> - 不应自动加载 normal/specular/occlusion
这一步是解决“模型材质导入过重”的关键。
18. 依赖系统设计
18.1 为什么一定要有依赖系统
没有依赖系统,就无法正确回答:
obj改了要不要重导?mtl改了要不要重导?- 外部贴图改了要不要重导?
- importer 设置改了要不要重导?
- 导入器版本升级了要不要重导?
18.2 依赖类型
初版至少支持:
- 主源文件依赖
- 侧车文件依赖
- 资产依赖
- importer 版本依赖
18.3 以 OBJ 为例
一个 backpack.obj 的导入依赖可能包含:
backpack.objbackpack.mtl- 由
.mtl解析出的外部贴图路径 ModelImporter的导入设置 hashModelImporter版本号
18.4 失效规则
以下任一变化应触发 reimport:
- 源文件内容 hash 变化
.meta设置变化- importer version 变化
- 依赖文件内容变化
- 依赖资产引用变化
- artifact 文件缺失
- artifact schema version 不匹配
19. 异步导入与异步加载设计
19.1 当前问题
当前 AsyncLoader 不是真正的后台导入系统。
19.2 新的任务模型
建议分成两类任务:
Import Task
负责:
- 检查 artifact 是否失效
- 读取源文件
- 执行 importer
- 写 Library
Runtime Load Task
负责:
- 从 artifact 读取
- 创建运行时对象
19.3 初版落地策略
建议分两阶段:
第一阶段
- 先做正式持久化 Library
- artifact miss 时允许同步 reimport
- 目标是第二次打开同资源时显著加速
第二阶段
- 引入后台 import 队列
- scene 打开时只恢复引用并排队资源
- 用 placeholder 和进度状态代替主线程长阻塞
这样风险更低,也更容易调试。
20. Editor 侧流程重构
20.1 打开项目
打开项目时应执行:
- 确保
Assets/存在 - 确保
Library/存在 - 扫描
Assets/ - 自动补齐缺失
.meta - 构建或修复
SourceAssetDB - 校验
ArtifactDB - 清理明显损坏或孤立的 artifact 记录
20.2 新增/导入文件
新文件出现时:
- 自动生成
.meta - 分配
AssetGUID - 进入
SourceAssetDB - 标记为
NeedsImport
20.3 重命名/移动文件
在 editor 内部重命名/移动时:
- 文件与
.meta一起移动 AssetGUID保持不变- 只更新路径索引
- scene 不需要改引用
这是新系统最核心的收益之一。
20.4 删除文件
删除文件时:
- 从
SourceAssetDB标记删除 ArtifactDB中对应 artifact 标记为 orphan- 后台或手动 GC 清理 artifact 文件
20.5 Reimport
需要支持:
- Reimport 当前资产
- Reimport All
- 查看导入日志
- 查看当前 artifact key
- 查看依赖列表
21. 场景与序列化迁移方案
21.1 目标
把 scene 与 prefab 从路径引用迁移到 AssetRef。
21.2 迁移策略
推荐采用三阶段迁移:
阶段 A:双读
- 读新格式
- 读不到则回退旧路径格式
阶段 B:双写
- 新保存写
guid + localID - 可选保留
pathFallback
阶段 C:单写
- 正式只写新格式
- 旧格式仅保留读兼容
21.3 迁移工具
需要提供一个项目级迁移工具:
- 扫描所有 scene / prefab / material
- 尝试把路径解析成
AssetGUID - 找不到则报错并生成报告
- 成功后回写新引用
22. 对当前 Backpack 问题的直接收益
以 backpack.obj 为例,重构后流程将变为:
第一次导入
ModelImporter读取backpack.obj- 解析
backpack.mtl - 为模型生成
Meshartifact - 为默认材质生成
Materialsub-asset - 外部贴图只写成
AssetRef<Texture> - 贴图由各自的
TextureImporter生成单独 artifact - 导入结果写入
Library/Artifacts
之后打开 scene
- scene 只恢复
AssetRef<Mesh> - editor 查
ArtifactDB - 直接从
xcmesh读取 mesh - 材质只恢复引用
- forward pipeline 只在需要时解析
baseColorTexture - 不再重新解析原始
obj - 不再重新解码所有原始
jpg/png
也就是说:
- 首次导入仍然可能慢
- 但第二次及以后,scene 打开不应再把 OBJ/贴图导入当场重做
23. 推荐的模块拆分
23.1 Engine 层新增/调整
建议新增或调整以下模块:
engine/include/XCEngine/Core/Asset/
├── AssetGUID.h
├── AssetRef.h
├── ArtifactKey.h
├── ArtifactManifest.h
├── IArtifactLoader.h
└── RuntimeAssetResolver.h
engine/src/Core/Asset/
├── AssetGUID.cpp
├── AssetRef.cpp
├── ArtifactKey.cpp
├── ArtifactManifest.cpp
└── RuntimeAssetResolver.cpp
23.2 Editor 层新增模块
建议新增:
editor/src/AssetPipeline/
├── AssetMetaSerializer.h/.cpp
├── SourceAssetDatabase.h/.cpp
├── ArtifactDatabase.h/.cpp
├── ArtifactStore.h/.cpp
├── AssetImporterRegistry.h/.cpp
├── TextureImporter.h/.cpp
├── ModelImporter.h/.cpp
├── MaterialImporter.h/.cpp
├── AssetDependencyScanner.h/.cpp
├── ImportScheduler.h/.cpp
├── ImportLogger.h/.cpp
└── ProjectAssetScanner.h/.cpp
23.3 对现有模块的调整
ResourceManager从“源文件加载器入口”改为“artifact runtime cache”MeshLoader拆分TextureLoader拆分MaterialLoader拆分MeshFilterComponent改为存AssetRef<Mesh>MeshRendererComponent改为存AssetRef<Material>ProjectManager扫描时支持.metaScene序列化升级版本
24. 分阶段实施计划
阶段 0:基础类型与工程边界收口
目标:
- 引入
AssetGUID - 引入
AssetRef - 把“路径 hash 充当资产身份”的设计正式停止
产出:
AssetGUID类型AssetRef类型ResourceGUID与AssetGUID的职责拆分
当前状态:
- 已完成。
- 已新增
AssetGUID/AssetRef基础类型,并开始接入运行时加载链路。
阶段 1:.meta 与 SourceAssetDB
目标:
- 为
Assets中所有文件/文件夹补齐.meta - 建立
guid/path/meta/importer索引
产出:
.meta读写器- 资产扫描器
SourceAssetDB- 资产移动/重命名时 GUID 保持不变
当前状态:
- 已完成。
- 已实现
.meta自动生成、读取、写回与资产扫描。 - 已在
ProjectManager中处理.metasidecar 的删除、移动、重命名与隐藏显示。
阶段 2:Artifact Store 与 ArtifactDB
目标:
- 把导入结果正式落盘进
Library/Artifacts - 用
ArtifactDB记录导入产物和依赖
产出:
ArtifactKeyArtifactDBArtifactStore- 依赖与失效判断
当前状态:
- 已完成。
- 已落地
Library/SourceAssetDB、Library/ArtifactDB、Library/Artifacts三层结构。 - 已实现基于
sourceHash/metaHash/importerVersion/size/writeTime的失效判断。
阶段 3:TextureImporter
目标:
- 纹理先走通从源文件到 artifact 的闭环
产出:
TextureImporterTextureArtifactLoaderxctex格式
当前状态:
- 已完成。
- 当前纹理资源首次导入时会生成
xctex,后续优先从 artifact 读取。
阶段 4:ModelImporter
目标:
- 模型导入改为 artifact 生成,不再 runtime 直接读源
obj
产出:
ModelImporterxcmesh格式- 默认材质 sub-asset
- 外部贴图改成
AssetRef<Texture>
当前状态:
- 已完成初版。
- 当前模型首次导入时会生成
main.xcmesh与关联texture_N.xctex。 - 仍未完成“外部贴图全局去重 + 材质独立 artifact + 真正按引用延迟加载”的最终形态,因此这里只能算阶段 4 初版完成。
阶段 5:Material 系统 lazy 引用化
目标:
- 材质不再一加载就拉起所有纹理
产出:
xcmat- 纹理引用延迟解析
Materialruntime 结构调整
当前状态:
- 部分完成。
- 已补齐材质 artifact 读写所需的 tag/property/texture binding 访问接口。
xcmat尚未落地,贴图仍是 eager load,不是最终目标状态。
阶段 6:Scene / Component 引用迁移
目标:
- 路径引用改成
guid + localID
产出:
- 新 scene 序列化格式
- 旧路径兼容读取
- 迁移工具
当前状态:
- 部分完成。
MeshFilterComponent与MeshRendererComponent已双写path + AssetRef,并保留旧数据兼容。- Scene 全量序列化升级、统一迁移工具、其他组件资产引用迁移尚未完成。
阶段 7:异步导入与异步加载
目标:
- 打开 scene 主线程只恢复引用,不做大规模阻塞导入
产出:
- import task 队列
- runtime load task 队列
- placeholder 与状态提示
当前状态:
- 未开始。
- 这是当前 editor 仍会被大
obj场景卡住的首要剩余任务。
阶段 8:清理、GC、工具与可视化
目标:
- 系统可维护、可调试、可清理
产出:
- orphan artifact 清理
- Reimport All
- 依赖图查看
- 导入日志面板
当前状态:
- 未开始。
25. 测试与验证方案
25.1 单元测试
必须补这些测试:
.meta缺失时自动生成 GUID- 重命名/移动后 GUID 保持不变
- 同一资产不同 importer 设置产生不同 artifact key
- source 改变时 artifact 失效
- 依赖贴图变化时 material 或 model 重新判定
- sub-asset localID 稳定性
- scene 新旧格式兼容读取
25.2 集成测试
必须补这些流程测试:
- 首次打开 backpack scene 触发导入
- 第二次打开 backpack scene 不再读取原始
obj - 仅改
diffuse.jpg时,不重新导入无关资产 - 移动模型文件后 scene 仍能打开
- 删除 artifact 后系统能够自动重建
25.3 性能验证
必须记录的指标:
- 首次导入耗时
- warm 打开 scene 耗时
- 主线程阻塞时间
- texture 解码时间
- model import 时间
- GPU 首帧上传时间
建议加入正式 instrumentation:
Scene openAsset resolveImport taskArtifact loadGPU upload
26. 风险与注意事项
26.1 不能再把路径当 GUID
这是本次重构的底线。如果不先解决这个问题,后面所有缓存都会在重命名/移动后变脆。
26.2 sub-asset LocalID 稳定性很重要
如果 obj 重导后 mesh localID 改了,即使 AssetGUID 不变,scene 里的引用仍然会断。
因此 localID 的生成规则必须:
- 基于稳定逻辑路径
- 不依赖运行时顺序
- 在 importer 内版本化
26.3 初版不要急着做复杂压缩
真正需要优先解决的是:
- 导入和加载分离
- Library 持久化
- scene 引用改成 GUID
纹理压缩和高级优化可以后做。
26.4 当前 Preview 缓存可以暂不并入新 Library
当前 editor 已经有 .xceditor/thumbs 预览缓存。
初版资产导入缓存系统可以先不动它,后续再考虑统一到 Library/PreviewCache。
26.5 需要预留 schema/version
以下内容都必须版本化:
.metaformat version- artifact manifest version
xcmesh/xctex/xcmatformat version- importer version
- project Library cache version
否则后续升级会非常痛苦。
27. 最终预期结果
这次重构完成后,系统应达到以下效果:
- 打开 scene 时,不再直接解析源
obj/png/jpg - 资产改名或移动后,scene 引用仍然稳定
- 首次导入慢,但只慢一次
- 后续打开 scene 优先走
Library Mesh、Material、Texture的职责边界清晰- editor 可以正式支持 Reimport、依赖跟踪、缓存清理
- 后续扩展 prefab、动画、打包、后台导入时,不需要推倒重来
一句话总结:
这次要做的不是“给现在的 ResourceManager 加缓存”,而是把 XCEngine 的资产系统重构成:
Assets + .meta
->
SourceAssetDB
->
Importer
->
ArtifactDB + Artifacts
->
Artifact Loader
->
ResourceManager
->
Scene / Renderer
只有这样,当前“打开 scene 时同步导入 obj 模型导致卡顿”的问题,才会被系统性解决,而不是被局部绕开。