流式加载、内容挂载与内存回收
范围:streaming/、scene/streamer/、场景内容加载和挂载接口。总入口:Summary。
1. 设计思路#
开放世界的资源需求同时来自摄像机、移动预测、人口、任务、过场、室内与内容包。游戏层的任务是把这些需求表达为有来源、有优先级和生命周期的请求,并在内存压力下选择可回收对象;文件 IO、模块存储和通用资源装载由 RAGE streaming 框架提供。
CStreaming 是游戏层门面。它使用 (strLocalIndex, module) 标识模块内部资源,再通过 module manager 转为统一 strIndex。RequestObject 的实现直接将转换后的索引交给 strStreamingEngine::GetInfo();因此不能把不同模块中的相同整数索引当作同一资源。
2. 请求与资源状态#
HasObjectLoaded 检查 STRINFO_LOADED;IsObjectRequested 和 IsObjectLoading 分别检查请求与装载中状态。三者语义不同。请求成功只说明请求被处理,调用方仍需要轮询或按约定等待就绪,不能立即访问尚不存在的 drawable、碰撞或动画内容。
streaming.h 对错误索引类型设置模板陷阱,意图在编译期阻止把不匹配的索引传入强类型接口。读取这组 API 时必须保持索引类型、模块和资源类别的一致性。
任务、过场、室内有各自的需求释放函数;另有 not deletable、do not defrag 等标志。这体现了“资源已不被当前调用者需要”与“资源可以立刻删除”的差别:其他来源及依赖仍可能持有它。
3. 每帧推进与同步加载#
CStreaming::Update 的关键环节包括设置 burst mode、回收更新、预加载更新、处理异步文件,再调用框架 streaming 更新。burst mode 的判断引用画面淡出、暂停渲染阶段或非默认渲染函数,说明系统会根据呈现状态调整吞吐策略;这并不等于任何淡出都保证资源立即就绪。
系统同时提供 LoadAllRequestedObjects、Flush 和请求清理接口,回放配置中还有带超时的同步装载。它们支持需要建立明确就绪边界的流程,但会改变帧时间和推进方式。普通玩法若频繁滥用同步加载,会削弱异步 streaming 的收益;是否存在实际卡顿需要运行观测,本文不把该风险当作现有缺陷。
关卡初始化、packfile 初始化和会话关闭的层次也不同。ShouldLoadStaticData 的注释明确区分首次装载与游戏重启,避免重载不变的数据;调试配置可要求重载 packfile。
4. 场景需求与专用 streamer#
| 需求来源 | 相关入口 | 为什么单独建模 |
|---|---|---|
| 摄像机与地图 | CGameScan、scene streamer | HD/LOD、室内和速度方向具有不同空间范围 |
| 环境人口 | populationstreaming | 下一批 Ped/Vehicle 模型取决于区域及人口选择 |
| 行为动画 | Situational/Prioritized ClipSet Streamer | 行为需要的动画不能只由几何可见性推导 |
| 脚本 | ScriptPreload、request list | 任务可在目标位置可见前提出显式需求 |
| 特定范围 | BoxStreamer、zoned asset manager | 区域进入和离开决定资源需求 |
| 大范围切换 | IslandHopper、LoadScene | 场景迁移需要更强的准备与状态控制 |
目录划分证明这些不同策略存在;具体优先级和预取距离由相应实现、配置和平台决定,本文不统一假设为一个固定半径。
5. 内存回收是带语义的选择#
CStreamingCleanup 明确区分 CLEANUP_ABORTED、CLEANUP_FAILED 和 CLEANUP_SUCCESS。其中 aborted 可以表示目标资源不值得为它释放当前内容,而 failed 表示未能释放足够空间。将二者合并为“磁盘加载失败”会误导定位。
接口同时区分 virtual 与 physical size,提供依赖大小查询、分配碎片判断、最低评分实体选择、递归依赖删除和对象数量控制。这里的 virtual/physical 是资源与分配器接口中的分类;不能仅凭字段名直接把它们等同于操作系统虚拟内存和实际系统 RAM。
回收不仅释放底层资源,还会处理实体、drawable 引用、车辆与角色等消费者。DeleteEntityAndDependencies、RecurseDeleteDependencies 和 DeleteLeastUsed 揭示了依赖关系的重要性:只删除一个看似很大的资源,若还有消费者引用,可能既无法释放空间又破坏一致性。
WasLastAllocationFragmented 将总量不足与碎片问题分开;WasLastAllocationAborted 保留策略拒绝原因;churn protection 相关字段则说明系统关注反复加载/卸载造成的抖动。性能分析应同时观察请求数量、优先级、存活引用、实体数量、碎片与 churn,而不是只看总内存。
6. 内容包与数据文件挂载#
CDataFileMgr 描述 data files、content change sets 和带 priority 的 change-set action;change set 可以涉及纹理、数据文件、地图变更以及加载屏幕要求。CDataFileMountInterface 为不同数据文件类型提供装载适配,CDataFileMount 按类型注册接口。
这相当于把“内容包需要哪些配置变化”与“某类型的数据如何进入具体模块”分离。车辆配置、地图内容与其他元数据可以由各自模块消费,挂载层统一协调切换。ExtraContent、MountableContent 还连接 DLC 与云端/平台内容来源;存在这些客户端接口并不意味着仓库包含对应服务端或全部内容资源。
维护时需确保数据结构、挂载/卸载对称性和内容切换顺序一起变更。只让一个文件进入 packfile,未必完成模型注册、元数据挂载和场景实例更新。
7. 典型问题的追踪顺序#
| 表现 | 优先区分的边界 |
|---|---|
| 请求了模型却无法创建对象 | 索引/模块正确性 → 已请求/加载中/已加载 → 原生创建校验 |
| 高速移动出现空洞 | 空间需求与速度预取 → IO/依赖进度 → 内存拒绝/回收 → 场景实例建立 |
| 切换任务后内存持续占用 | 任务需求标志 → 实体与 drawable 引用 → 共享依赖 → 可删除状态 |
| 内容反复出现和消失 | 评分、churn protection、范围边界与瞬时内存压力 |
| DLC 数据不生效 | 内容选择 → change set → mount interface → 具体消费者 |
这些是源码驱动的诊断框架,未执行资源装载或性能测试。
8. 源码依据#
| 源码 | 关键符号 |
|---|---|
| streaming.h | 强类型索引接口、需求标志、状态查询 |
| streaming.cpp | Update 589 行附近;RequestObject 982 行;HasObjectLoaded 1041 行 |
| streamingcleanup.h | 回收结果、评分、碎片、递归依赖删除 |
| GameScan.cpp | 多种 streaming cull shapes |
| DataFileMgr.h | ContentChangeSet、ChangeSetAction、MountInterface |
| ExtraContent.h | 附加内容与服务连接 |