CD Code Digest Hub /GTA V

文件设备、资源格式、解析器与内容装配

范围:Base file/data/paging/parser/parsercore/xmldata,Framework PSO Store 和 packfile 管理。离线生产工具另见Tooling。静态分析,未运行。入口:Summary。

8 分钟阅读 10 个章节 1 张图

1. 两种表示:方便生产与方便运行#

引擎通过元数据/schema、资源编译和平台打包,把便于编辑的数据变成容易装载的连续或分块内存布局。运行时尽量减少文本解析、逐对象小分配和重复类型查询,但同时承担版本、布局、指针修复和引用生命周期的复杂度。

不是所有资源都严格经过图中每一个方框:图表示主要可组合路径,PSO 与传统 resource construction 有不同装入协议。

2. fiDevice:路径路由与设备能力#

fiDevice 用设备前缀/挂载把文件访问路由到具体实现,支持本地、包、相对路径、网络/内存等设备。它把路径命名空间和物理介质解耦,让高层用相同 Open/Read/Close 访问不同来源。

OpenBulk 返回附加 bias,归档可以用真实底层文件加偏移实现读取,避免为未压缩包内每个文件维护独立 seek 状态。普通 handle 与 bulk handle 的合法操作不同,接口要求用对应 CloseBulk。

RootDeviceId、物理排序键和 LSN 等又把介质信息暴露给需要优化顺序读取的上层。这是一种“通用接口+性能能力透传”,不是完全假装所有存储设备一样快。

3. RPF 是容器,资源对象是另一层#

fiPackfile 提供目录项、名字、索引、mount 和物理排序信息。包负责文件集合、偏移和相关编码;对象内部如何恢复 drawable、动画或地图,由资源系统/module 负责。

因此 RPF 文件打开成功,不等于里面的资源已经构造或依赖已经满足。包内容布局会影响 streaming 请求邻近性;更新包挂载顺序又会影响同名路径最终解析到哪份内容。具体项目覆盖策略应继续看 packfilemanager,不能仅按磁盘文件名判断生效版本。

安全/加密相关字段属于格式与平台支持,本报告不导出密钥或操作受保护内容,也不以存在这些文件推断服务端信任模型。

4. datResourceMap 与 resource constructor#

资源 map 保存 VirtualCount、PhysicalCount、root chunk 和多个 datResourceChunk。虚拟块在前,物理块在后;资源本来记录的地址与当前分配地址通过 fixup 映射关联。

datResource::Fixup 修复指针,Place 则修复地址并调用对象的资源构造入口。许多类的 datResource& 构造器不同于普通默认构造器:其任务是解释已有内存,而不是清零并重新分配所有字段。

这能大幅减少对象图装载时的分配次数,但对 ABI 敏感。结构增减字段、虚表、指针宽度、对齐、endian 和构建版本变化都需要资源版本/构建规则配套,不能把 C++ 内存转储视为天然跨平台文件格式。

pgRef/pgOwner 和 known references 进一步服务重定位与资源搬移,所有权与地址追踪的区别见CoreMemory。

5. Parser 不只是 XML 读取器#

parser 层包含 structure/member 描述、array/map/enum/string/vector/matrix 等类型,visitor 可以承担初始化、校验、XML、工具 widgets 等操作;.psc 的代码生成链路把描述与运行时类型联系起来。

这种反射式结构描述让编辑器、资源编译器和运行时共用数据定义,减少各写一份字段映射。代价是生成代码、手写 C++ 和工具版本必须一致。修改生成结果而不修改源 schema 会在下次生成时丢失,也可能让资源布局不匹配。

6. PSO 的快速路径与兼容路径#

psoFile 的统计明确区分 layout matching 与 mismatch;不匹配时可进入 memberwise loading。loadAction 分成不预处理、准备 parser loading,以及假定 in-place。

PSOLOAD_ASSUME_IN_PLACE_LOADING 文档明确警告:布局不匹配会产生错误数据或内存破坏。因此 in-place 是经过校验才能使用的性能契约,不是所有资源都可以默认开启的开关。

PSO 的装载状态还区分读取失败、空文件、缺数据、checksum 与文件大小不匹配、冗余/未知数据。应把失败原因向内容构建和 QA 暴露,避免把所有失败合并成“缺文件”。

7. Store 负责发布与存储所有权#

fwPsoStoreLoadInstance 区分 XML、in-place 自分配/外部存储和非原地装载,OwnsStorage() 根据状态决定谁释放数据。fwPsoStorePlacementCookies 用于跨线程传递 load instance;SetResource 等最终步骤再完成主线程交接。

这解决了一个关键问题:同样的解析对象可能只是指向原始资源块,也可能是重新分配的 C++ 对象。若不区分 ownership,关闭时容易重复释放或留下引用悬空。

psoInitFromResource 文档也明确要求底层 resource data 活到 psoFile 用完;parser wrapper 存活不代表源字节被自动延长生命周期。

8. 性能与兼容性的真实取舍#

设计收益限制
连续/分块资源少分配、读取可合并、局部性好峰值大、局部更新困难
原地装载减少逐成员拷贝布局必须兼容,版本管理严格
成员式回退更宽的兼容空间解析和分配成本可能明显上升
离线处理把 BVH、压缩、索引成本移出帧循环构建耗时、缓存失效和工具依赖
包内排序改善介质访问局部性DLC/补丁和资源复用可能破坏邻近性

需要特别避免“零拷贝”误称:in-place 对象解析减少拷贝,不代表读取、解压、GPU 上传与 defrag 完全没有拷贝。

9. 对新项目的启发#

统一资产 ID、schema 版本与构建指纹,能从运行资源追溯源文件、工具版本和参数。对每类资产提供可报告的依赖表、最大内存、装配成本及迁移策略。是否允许 runtime memberwise 兼容,应根据产品更新要求决定;开发工具可灵活,发行关键资源可以更严格。

验证应包括错版本、32/64 位布局差异、缺依赖、取消中断、包覆盖、压缩失败和重复装卸。监测 layout mismatch 次数,避免内容回退慢路径在无人察觉时变成加载性能退化。本次未调用任何编译器、资源生成器或随附可执行工具。

10. 源码证据#

文件关键内容
device.h68–138 设备抽象、Open/Bulk 契约
packfile.h容器结构、挂载、目录项和排序信息
resource.hdatResourceMap、Fixup/Place 与 resource constructor
rscbuilder.h资源构建接口
structure.hparser 类型描述
psofile.h22–77 layout、失败状态及 in-place 契约
psostore.hownership、placement cookies 与装载 flags
psc.targets.psc 与 parCodeGen 构建声明,仅阅读未执行

关联:Streaming、Tooling。