CD Code Digest Hub /GTA V · v2

流式引擎:rage/framework/src/streaming

102 分钟阅读 14 个章节 3 张图

本页解释 RAGE Framework 的流式(streaming)子系统:一个把磁盘/光驱上的数万个文件(模型、纹理字典、动画、地图块、脚本……)按需读入、放置(placement)、引用计数、淘汰与碎片整理的统一引擎。它的核心是:

  • 一个全局、扁平的 strIndex 空间,每个可流式对象一个 strStreamingInfo(8 字节状态记录);
  • 一组 strStreamingModule(每种资产一个,如 fwTxdStore、fwDrawableStore、CModelInfoStreamingModule),负责把读入的字节变成运行时对象;
  • strStreamingInfoManager 维护请求/加载中/已加载三类集合与依赖计数;
  • strStreamingLoader(光驱/硬盘各一个)按磁头位置挑选请求、分配内存、发起异步读、在主线程或放置线程上完成放置;
  • strPackfileManager 管理 RPF 档案(档案本身也是一个流式对象);
  • strStreamingAllocator 与 strDefragmentation 管理资源堆。

游戏层的“要什么”(场景流送器、人口流送、脚本请求、清理策略)在 ../05-world/game-streaming-and-scene-streamer.md;本页只讲框架机制。

背景与相关页面#

相关源码文件#

文件内容
streaming/streamingdefs.h配置宏、请求/内部标志、引用种类、容量常量、strIndex/strLocalIndex
streaming/streamingengine.h/.cppstrStreamingEngine(静态门面)、strStreamingInterface(游戏回调)
streaming/streaminginfo.h/.cppstrStreamingInfo、strStreamingInfoManager(请求、状态机、依赖、淘汰、注册)
streaming/streamingmodule.h/.cpp、streamingmodulemgr.h/.cpp模块基类与模块管理器(索引区间、扩展名映射)
streaming/streamingloader.h/.cppstrStreamingLoader、strStreamingLoaderManager、放置线程
streaming/packfilemanager.h/.cppstrPackfileManager、strPackfileStreamingModule("Archive" 模块)
streaming/streamingallocator.h/.cpp、defragmentation.h/.cpp资源分配与碎片整理
streaming/streamingrequest.h/.cppstrRequest/strRequestArray:RAII 风格的请求句柄
streaming/streaminglive.*、CacheLoader.*、requestrecorder.*(+.psc)、streaminginstall*、strindexdebugmanager.*、streamingvisualize.*热重载、缓存加载、请求录制、PS3 安装、调试
fwtl/assetstore.hfwAssetStore/fwAssetRscStore:大多数模块的模板实现
fwsys/fileExts.h、fwsys/game.cpp文件类型 ID/扩展名、框架模块注册顺序
游戏侧:game/streaming/streaming.cpp、game/Core/game.cpp、game/renderer/renderThread.cpp、game/modelinfo/ModelInfo.cpp引擎初始化、帧内调用点、模型模块派生

1. 组件总览#

图中证据:引擎持有分配器、信息管理器、加载器、(可选)碎片整理器与热重载的静态实例 src/dev_ng/rage/framework/src/streaming/streamingengine.h:142-196;游戏回调接口 strStreamingInterface(MakeSpaceForMap、LoadAllRequestedObjects、内存报告等)src/dev_ng/rage/framework/src/streaming/streamingengine.h:28-81,游戏实现把 MakeSpaceForMap 转发给 CStreamingCleanup src/dev_ng/game/streaming/streaming.cpp:131-148;分配时调用该回调 src/dev_ng/rage/framework/src/streaming/streamingallocator.cpp:666;模块管理器挂在信息管理器内 src/dev_ng/rage/framework/src/streaming/streaminginfo.h:750;加载器管理两个设备加载器、放置线程与放置队列 src/dev_ng/rage/framework/src/streaming/streamingloader.h:328-361;读请求经 pgStreamer::Read src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:2102-2110;RPF 内文件通过 RegisterObject 进入信息表 src/dev_ng/rage/framework/src/streaming/packfilemanager.cpp:1211-1221;碎片整理调用模块 Defragment src/dev_ng/rage/framework/src/streaming/defragmentation.cpp:977-981。


2. 索引空间与模块#

2.1 strIndex / strLocalIndex#

  • strIndex 是全局下标(u32,无效值 0xffffffff);strLocalIndex 是模块内槽号(s32)src/dev_ng/rage/framework/src/streaming/streamingdefs.h:146-148、src/dev_ng/rage/framework/src/streaming/streamingdefs.h:179-249。USE_PAGED_POOLS_FOR_STREAMING 为 0 src/dev_ng/rage/framework/src/streaming/streamingdefs.h:34,因此 strIndex 是简单整数而非分页池下标(分页实现仍在代码中,以 #if 保留)。
  • 模块注册时,AddModule 把模块的 m_indexOffset 设为上一个模块的 offset + size,即每个模块在全局空间占一段连续区间;同时登记“扩展名哈希 → 模块 ID”,并断言模块数 ≤ MAX_STREAMING_MODULES(30) src/dev_ng/rage/framework/src/streaming/streamingmodulemgr.cpp:182-239、src/dev_ng/rage/framework/src/streaming/streamingdefs.h:129。
  • 互转:GetStreamingIndex(local) = m_indexOffset + local,GetObjectIndex(global) = global - m_indexOffset src/dev_ng/rage/framework/src/streaming/streamingmodule.h:640-649、src/dev_ng/rage/framework/src/streaming/streamingmodule.h:694-704;由全局下标找模块用缓存的区间表二分查找,越界 Quitf(ERR_STR_MOD_INFO) src/dev_ng/rage/framework/src/streaming/streamingmodulemgr.cpp:242-301。
  • 所有模块注册完后,AllocateStreamingInfoArray() 按模块总条目数一次性分配 strStreamingInfo 数组(flex 内存),并禁止再添加模块(assert 构建)src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:360-395;游戏在 CStreaming::Init(INIT_CORE) 调用它 src/dev_ng/game/streaming/streaming.cpp:400-404。

注册顺序(决定 strIndex 区间布局):strPackfileManager::Init 先注册 "Archive" 模块 src/dev_ng/rage/framework/src/streaming/packfilemanager.cpp:197-208(由 strStreamingEngine::Init 调用 src/dev_ng/rage/framework/src/streaming/streamingengine.cpp:133);随后 fwGame::RegisterStreamingModules 依次注册 Dwd、Drawable、Txd、Fragment、Cloth、MetaData、MapTypes、MapData,再调游戏接口 src/dev_ng/rage/framework/src/fwsys/game.cpp:27-41;游戏接口注册 PoseMatcher、BlendShape(条件)、ClipDictionary、CutScene、ExprDict、PmDictionary(条件)、Scaleform、StaticBounds、StreamedScripts、ptfx、NetworkDef、FrameFilter、WaypointRecording、VehicleRecording、ModelInfo、PathFind、PathServer src/dev_ng/game/Core/game.cpp:558-583。CModelInfo::RegisterStreamingModule 把返回的模块 ID 写回 fwArchetypeManager src/dev_ng/game/modelinfo/ModelInfo.cpp:1658-1662。

2.2 strStreamingModule 接口#

模块基类构造参数:名称、文件类型 ID(-1 表示无扩展名映射)、条目数、是否需要临时内存、能否碎片整理、期望资源版本、每页元素数(分页池关闭时被忽略)src/dev_ng/rage/framework/src/streaming/streamingmodule.h:67-111。头注释概括了最小集成:资源模块覆写 Register/Remove/Load 或 PlaceResource;支持碎片整理需覆写 GetPtr/Defragment;支持引用计数需覆写 AddRef/RemoveRef/GetNumRefs/GetDependencies src/dev_ng/rage/framework/src/streaming/streamingmodule.h:46-55。

虚函数默认由谁调用 / 作用证据
Register(name) / FindSlot(name)纯虚RPF 注册时按扩展名找到模块后调用src/dev_ng/rage/framework/src/streaming/streamingmodule.h:126-138
Load(index, data, size)返回 false非资源文件或 dummy 完成时src/dev_ng/rage/framework/src/streaming/streamingmodule.h:153
PlaceResource(index, map, header) / SetResource(index, map)空资源放置(可在放置线程)/ 主线程收尾src/dev_ng/rage/framework/src/streaming/streamingmodule.h:159-162
Remove(index)空RemoveObject 卸载已加载对象src/dev_ng/rage/framework/src/streaming/streamingmodule.h:142
GetPtr / GetDataPtr / GetResourceNULL取对象指针 / 释放内存时取数据指针 / 碎片整理取 pgBasesrc/dev_ng/rage/framework/src/streaming/streamingmodule.h:167-192
Defragment / DefragmentComplete / DefragmentPreprocess空碎片整理重新构造资源src/dev_ng/rage/framework/src/streaming/streamingmodule.h:179-187
AddRef / RemoveRef / GetNumRefsGetNumRefs 默认返回 1引用计数;默认 1 意味着未实现引用计数的模块永不被 LRU 淘汰src/dev_ng/rage/framework/src/streaming/streamingmodule.h:236-245
GetDependencies(index, out, max)0依赖展开(见 §3.4)src/dev_ng/rage/framework/src/streaming/streamingmodule.h:255
RequestExtraMemory / ReceiveExtraMemory空额外内存(与文件一起分配,放置时移交)src/dev_ng/rage/framework/src/streaming/streamingmodule.h:278-284
RequiresTempMemory构造参数读入缓冲在放置后释放src/dev_ng/rage/framework/src/streaming/streamingmodule.h:320
CanPlaceAsynchronously / PlaceAsynchronouslyfalse / 空是否在放置线程上放置src/dev_ng/rage/framework/src/streaming/streamingmodule.h:340-345
ModifyHierarchyStatusfalse层次化启用/禁用(IMAP 用)src/dev_ng/rage/framework/src/streaming/streamingmodule.h:196

非虚便捷方法:StreamingRequest(index, flags) = GetInfo().RequestObject(GetStreamingIndex(index), flags);StreamingRemove 同理调 RemoveObject src/dev_ng/rage/framework/src/streaming/streamingmodule.h:590-598;StreamingBlockingLoad 以 PRIORITY_LOAD|flags 请求后调用 LoadAllRequestedObjects(true) 阻塞到优先请求全部完成 src/dev_ng/rage/framework/src/streaming/streamingmodulemgr.cpp:136-149。

2.3 fwAssetStore:资产存储模板#

绝大多数模块是 fwAssetStore<T, S>:T 为运行时对象,S 为槽定义(默认 fwAssetDef<T>,含 T* m_pObject、引用计数、名字)src/dev_ng/rage/framework/src/fwtl/assetstore.h:56-126、src/dev_ng/rage/framework/src/fwtl/assetstore.h:168-181。

  • 引用计数:DETAILED_MODULE_REF_COUNT(= __DEV)时拆成 64 位联合体中的三段:渲染 12 位、脚本 9 位、其他 43 位(REF_DEFRAG 与 REF_RENDER 同值);否则一个 int src/dev_ng/rage/framework/src/fwtl/assetstore.h:32、src/dev_ng/rage/framework/src/fwtl/assetstore.h:63-116、src/dev_ng/rage/framework/src/streaming/streamingdefs.h:86-100。AddRef/RemoveRef 断言只在更新线程调用;RemoveRef 归零且对象已不在任何镜像中时(注释称“archaic condition”)会直接 Remove src/dev_ng/rage/framework/src/fwtl/assetstore.h:885-964。引用归零不会立即卸载——卸载由流式引擎的 LRU/清理决定。
  • 容量:FinalizeSize() 以 atStringHash(模块名) 查询 fwConfigManager::GetSizeOfPool,用结果分配 fwPool<S> 与名字注册表并更新模块 m_size;RegisterStreamingModule() 先 FinalizeSize() 再 AddModule(this) src/dev_ng/rage/framework/src/fwtl/assetstore.h:457-486。框架内各存储构造时 size 都传 CONFIGURED_FROM_FILE(=1),即槽数必须来自 gameconfig.xml src/dev_ng/rage/framework/src/fwsys/config.h:336(存储列表见 scene-graph-and-visibility.md §5.4)。
  • fwAssetRscStore:资源版本的子类,requiresTempMemory=false,CanPlaceAsynchronously() 恒为 true,PlaceAsynchronously 即 GetLoader().RequestAsyncPlacement(file, file.m_LoadingMap, resInfo) src/dev_ng/rage/framework/src/fwtl/assetstore.h:347-367、src/dev_ng/rage/framework/src/fwtl/assetstore.h:1324-1327。fwMapDataStore、fwMapTypesStore、fwMetaDataStore 也各自实现了异步放置(PSO 加载)src/dev_ng/rage/framework/src/fwscene/stores/mapdatastore.cpp:308、src/dev_ng/rage/framework/src/fwscene/stores/maptypesstore.cpp:573、src/dev_ng/rage/framework/src/fwsys/metadatastore.cpp:118。

2.4 文件类型与扩展名#

fwsys/fileExts.h 定义 33 个文件类型 ID(0 = RPF 档案、1 = drawable、2 = fragment、3 = DWD、4 = TXD、5 = 动画、…、14 = MAPDATA、29 = MAPTYPES、32 = 文本数据库)src/dev_ng/rage/framework/src/fwsys/fileExts.h:45-77,以及各平台的扩展名:例如 PC 64 位为 ydr/yft/ydd/ytd/ycd/ybn/…/ymap/ytyp(首字母 y),PC 32 位为 w*,并有平台无关扩展名 idr/itd/imap/ityp/rpf 等 src/dev_ng/rage/framework/src/fwsys/fileExts.h:79-118、src/dev_ng/rage/framework/src/fwsys/fileExts.h:324-353(其他平台的首字母前缀见同文件后续段落)。资源版本宏(如 DRAWABLE_FILE_VERSION = rmcDrawable::RORC_VERSION + 18)也在此 src/dev_ng/rage/framework/src/fwsys/fileExts.h:30-43。


3. strStreamingInfo:状态、标志与依赖#

3.1 记录布局(8 字节)#

每个 strStreamingInfo 只有 u32 m_handle(打包文件句柄;最高两位 10 表示“档案句柄”,低 30 位为档案号)与一个位域字:m_status:2、m_dependentCount:14、m_flags:16 src/dev_ng/rage/framework/src/streaming/streaminginfo.h:69-77、src/dev_ng/rage/framework/src/streaming/streaminginfo.h:200-205;STORE_FILE_SIZES(__DEV||__BANK)时额外存虚拟/物理尺寸 src/dev_ng/rage/framework/src/streaming/streaminginfo.h:49、src/dev_ng/rage/framework/src/streaming/streaminginfo.h:207-210。IsInImage() = 有有效句柄或是 dummy src/dev_ng/rage/framework/src/streaming/streaminginfo.h:146。

请求、加载中、已加载的附加数据不放在 strStreamingInfo 里,而是放在按 strIndex 索引的哈希表中:m_requestInfoMap(+双链表)、m_loadingInfoMap、m_loadedInfoMap(+LRU 双链表)、m_loadedPersistentInfoMap,另有 dummy 请求列表、每设备排序请求数组、RPF 覆盖表等 src/dev_ng/rage/framework/src/streaming/streaminginfo.h:709-735、src/dev_ng/rage/framework/src/streaming/streaminginfo.h:237-325。

容量(来自 fwConfigManager)src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:318-335:

配置键用途默认
MaxLoadedInfostrLoadedInfo 池CONFIGURED_FROM_FILE
ActiveLoadedInfo / ActivePersistentLoadedInfo已加载 / 持久已加载哈希表槽数(后者需素数)CONFIGURED_FROM_FILE
MaxLoadRequestedInfo请求池与每设备排序数组容量3000(DEFAULT_MAX_ACTIVE_REQUEST_INFO src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:281)
(固定)加载中表LOAD_SLOTS_PER_DEVICE × DEVICE_COUNT src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:297-298

加载表满时 Quitf(ERR_STR_INFO_2) src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:868-876;请求池满时 RequestObject 返回 false src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:987-991;排序数组满且清理已删除项后仍满则 Quitf(ERR_STR_INFO_1) src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:699-714。

3.2 状态机#

strResidentStatus 只有四个值:STRINFO_NOTLOADED=0、STRINFO_LOADED、STRINFO_LOADREQUESTED、STRINFO_LOADING src/dev_ng/rage/framework/src/streaming/streaminginfo.h:39-46。

图中证据:

  • RequestObject:对 NOTLOADED 对象,校验在镜像中且所有依赖在镜像中 → 请求档案 → 递归请求依赖 → AddRequest → SetStatus(LOADREQUESTED) → dummy 且依赖满足则立即 RemoveRequest+CompleteDummyRequest src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:1109-1320。
  • IssueRequest 成功发起读取后 SetObjectToLoading,它把请求表项移到加载中表并清 PRIORITY_LOAD src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:2180、src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:1960-2015。
  • SetObjectToLoaded:移出加载中表、清 FORCE_LOAD、按“无 DONTDELETE 且无依赖者”放入 LRU 已加载表,否则放入持久表,释放档案引用,SetStatus(LOADED) src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:2044-2114。
  • 失败路径 SetObjectToNotLoadedFromLoading src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:2018-2042,由 ConvertToObject 失败分支调用 src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:2563-2577。
  • RemoveObject:有依赖者的 LOADED/LOADING 对象拒绝删除;LOADED → 取消进行中的碎片整理、从已用内存表移除各块、从碎片整理器注销、先 SetStatus(NOTLOADED) 再 module->Remove(注释:防止 Remove 中再分配流式内存导致递归);LOADREQUESTED → RemoveRequest;LOADING 且 INTERNAL_PLACING → CancelRequest 并返回 true(延迟删除);否则取消读取 src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:1344-1631。

SetStatus 本身维护依赖计数:NOTLOADED → 非 NOTLOADED 时 AddDependentCounts,反向时 RemoveDependentCountsAndUnrequest;断言只在更新线程调用 src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:605-630。

3.3 标志#

请求标志(调用方可传入)与内部标志 src/dev_ng/rage/framework/src/streaming/streamingdefs.h:61-83:

位标志含义
0STRFLAG_DONTDELETE加载后不可删除,直到显式清除
1STRFLAG_FORCE_LOAD请求保持到加载完成(不被 purge)
2STRFLAG_PRIORITY_LOAD优先队列
3STRFLAG_LOADSCENE属于 load scene;load scene 期间不被 purge
4–7MISSION_/CUTSCENE_/INTERIOR_/ZONEDASSET_REQUIRED各子系统的“需要”位
8–14INTERNAL_LIVE/DUMMY/COMPRESSED/RESOURCE/DEFRAGGING/DONT_DEFRAG/PLACING内部状态

组合掩码:STR_DONTDELETE_MASK = DONTDELETE + 四个 REQUIRED;STR_FORCELOAD_MASK = DONTDELETE 掩码 + FORCE_LOAD;STR_PERSISTENT_MASK = LIVE/DUMMY/COMPRESSED/RESOURCE/DEFRAGGING/DONT_DEFRAG(重新请求时保留)src/dev_ng/rage/framework/src/streaming/streamingdefs.h:108-112。VerifyFlagChange 断言:LOADREQUESTED 期间不得改 DUMMY/PRIORITY;LOADED 期间不得直接改 DONTDELETE 掩码(必须走 SetRequiredFlag/ClearRequiredFlag 以便在 LRU 表与持久表间迁移)src/dev_ng/rage/framework/src/streaming/streaminginfo.h:91-100、src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:2145-2165。

已请求对象被再次请求时:只允许升级为优先(不允许降级);已加载/加载中对象请求时去掉优先位,并在新增 DONTDELETE 类位时调用 SetRequiredFlag src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:1013-1090。

3.4 依赖链示例:模型 → drawable → 纹理字典#

依赖完全由各模块的 GetDependencies 动态给出(上限 STREAMING_MAX_DEPENDENCIES = 100 src/dev_ng/rage/framework/src/streaming/streamingdefs.h:139,超出时 AddDependencyOutput 断言 src/dev_ng/rage/framework/src/streaming/streamingmodule.h:706-730):

层模块GetDependencies 返回证据
模型(dummy)fwArchetypeStreamingModule按 DrawableType:DWD / fragment / drawable 中的一个;ASSETLESS 无src/dev_ng/rage/framework/src/entity/archetypestreamingmodule.cpp:54-97
模型(游戏扩展)CModelInfoStreamingModule在上面基础上追加:动画字典、表情字典/表情集、ptfx 资产;行人追加道具/组件 DWD、元数据、布料、各运动 clip set;车辆追加 GetStreamingDependencies 与绳索纹理src/dev_ng/game/modelinfo/ModelInfo.cpp:224-404
drawable / DWD / fragmentfwDrawableStore / fwDwdStore / fwFragmentStore父 TXD(若有)src/dev_ng/rage/framework/src/fwscene/stores/drawablestore.cpp:210-219、src/dev_ng/rage/framework/src/fwscene/stores/dwdstore.cpp:250-259、src/dev_ng/rage/framework/src/fwscene/stores/fragmentstore.cpp:281-290
TXDfwTxdStore父 TXD(形成父纹理字典链)src/dev_ng/rage/framework/src/fwscene/stores/txdstore.cpp:116-125
任意 RPF 内文件(隐式)所在 RPF 档案("Archive" 模块对象)src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:1165-1234

因此请求一个模型会形成 模型(dummy) → drawable → TXD → 父 TXD …,每个文件节点还隐式依赖其 RPF 头。RequestObject 递归请求这些依赖时只传递 PRIORITY_LOAD 位(reqflags = flags & STRFLAG_PRIORITY_LOAD),不传递 DONTDELETE/FORCE_LOAD src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:1154-1156、src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:1236-1259——依赖的存活靠依赖计数而非标志。

注意 fwTxdStore::GetDependencies 与 fwDwdStore::GetDependencies 把 GetStreamingIndex(strLocalIndex(indexArraySize)) 作为 resourceIndex 参数传入,这个参数只用于 assert 诊断(ASSERT_ONLY(resourceIndex))src/dev_ng/rage/framework/src/fwscene/stores/txdstore.cpp:122、src/dev_ng/rage/framework/src/fwscene/stores/dwdstore.cpp:256、src/dev_ng/rage/framework/src/streaming/streamingmodule.h:706。看起来是笔误(应为 index),只影响诊断信息。

3.5 依赖计数与持久表#

  • 当对象离开 NOTLOADED(被请求)时,AddDependentCounts 让每个依赖 m_dependentCount++ 并把依赖(以及有依赖的自己)移入持久已加载表,从而退出 LRU 淘汰候选 src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:497-516。
  • 当对象回到 NOTLOADED,RemoveDependentCountsAndUnrequest 对每个依赖 --count;若归零:仍处于 LOADREQUESTED 且无 DONTDELETE 的依赖被直接 RemoveObject(级联取消请求),已加载且无 DONTDELETE 的依赖移回 LRU 表;非 FINAL 构建对递归深度 > 8 告警 src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:518-583。
  • 判断能否删除:IsObjectReadyToDelete = 无 DONTDELETE(及调用者给的忽略位)且依赖计数 0 且模块 GetNumRefs()==0 src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:2130-2141。

4. 加载器:从请求到放置#

4.1 结构与参数#

strStreamingLoaderManager 持有 ODD(光驱)与 HDD(硬盘)两个 strStreamingLoader(STREAMING_SCHEDULER_COUNT=2),一个放置线程与其消息队列(容量 32 × 设备数),以及“延迟放置请求”数组 src/dev_ng/rage/framework/src/streaming/streamingloader.h:249-364、src/dev_ng/rage/framework/src/streaming/streamingdefs.h:153-156。每个设备加载器有 LOAD_SLOTS_PER_DEVICE = 32 个 StreamingFile 槽、一个 FIFO 队列和一个异步放置列表 src/dev_ng/rage/framework/src/streaming/streamingloader.h:176-191、src/dev_ng/rage/framework/src/streaming/streamingdefs.h:128。StreamingFile 记录状态(IDLE/LOADING/CANCELED)、m_IsPlaced(放置线程置位)、m_Reissue、句柄、读 ID、加载映射与分配映射(后者是含额外内存的超集)、m_Finished(读完成回调置位)src/dev_ng/rage/framework/src/streaming/streamingloader.h:113-147。

可调参数(静态,BANK 可在 RAG 修改)src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:75-80、src/dev_ng/rage/framework/src/streaming/streamingdefs.h:118-128:

参数默认作用
ms_ExtremeStreaming_Enabledtrue关闭时每设备同时只允许 1 个挂起请求
ms_MaxPendingRequestThreshold30每设备最多挂起文件数(含等待放置)
ms_ExtremeStreaming_MaxPendingKbMAX_PENDING_KB:Orbis 32768,其他 8192挂起数据量上限(KB)
ms_ExtremeStreaming_MaxConvertMsMAX_CONVERT_MS = 8每帧转换/发起请求的时间预算
ms_LsnTolerance[2]0x4000000连续请求间允许的 LSN 间隙
ms_MaxChainedRequests24(AREA51 时每帧改为 1)每次取候选的最大数
MAX_LOAD_PER_FRAME4RequestStreamFiles 单批最多新发请求

burst 模式(SetBurstMode)把挂起数据量视为无限、时间预算提升到 20ms src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:506-508。

4.2 每帧流水线#

strStreamingLoaderManager::LoadRequestedObjects 先重置 LRU 迭代器并快照“上一帧最后加载的对象”(之后的对象本帧不得被 LRU 删除,注释说明它们可能还没来得及加引用),再依次调两个设备加载器 src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:2787-2809、src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:919-922。单设备 LoadRequestedObjects src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:418-585:

  1. ProcessStreamFiles():从队列头取 m_Finished 的文件 ProcessFile,超过 MaxConvertMs 即停;AREA51 模式下依赖未满足的文件被推回队尾 src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:1308-1357。
  2. 挂起数 ≥ 阈值则提前返回。
  3. 循环:GetNextRequestsOnCd(磁头位置, 优先=true, …) 取候选,再 RequestStreamFiles 尽量发起,直到挂起数/数据量/时间任一达到上限 src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:490-567。磁头位置取“上次失败请求的位置”与 pgStreamer::GetLastRequestPosn 的较大者 src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:2662-2665。

候选选择 GetNextFilesOnCdNew src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:2249-2621:

  • 先处理 dummy 请求(只在设备 0 上做一次):依赖未全部 LOADED 则把 NOTLOADED 的依赖请求上(带同样的优先位),全部就绪就立即完成 dummy src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:2269-2338。
  • 再反复扫描排序请求数组,把仍为 NOTLOADED 的依赖和 RPF 档案请求上,直到不再新增 src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:2340-2411。
  • 光驱(或 -sortonhdd)按 LSN 排序后从磁头位置向外找;硬盘从上次位置轮转以求公平 src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:2416-2448、src/dev_ng/rage/framework/src/streaming/streaminginfo.h:700。只有依赖与档案都 LOADED 的请求才入选;需要排序时,与上一个入选文件的 LSN 间隙超过容差就停止(期望下一帧有更合适的请求)src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:2553-2582。
  • 优先请求不足时递归补充普通请求(注释说明优先请求可能有非优先依赖)src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:2607-2618。

发起 RequestStreamFiles src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:1418-1595:槽满即停;跳过已 LOADING/LOADED 或状态异常的项;档案未驻留则跳过(等待);若该索引在队列中处于 CANCELED,则“复活”该项(重新 SetObjectToLoading 并补偿多出的档案引用);否则要求所有依赖 LOADED 或 LOADING(任一不满足则中止整批),然后 AddNewRequest → IssueRequest,每批最多 MAX_LOAD_PER_FRAME。

IssueRequest src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:1972-2193:OpenFile → AllocateMemory(失败记录失败位置并返回)→ 若分配过程中为腾空间而删掉了本对象的最后一个依赖者导致它已回到 NOTLOADED,则释放内存放弃 src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:2081-2092 → pgStreamer::Read(..., SignalCallback, &file, device, flags)(光驱读用 UNCACHED src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:1597-1600)→ 入队、更新挂起计数与数据量 → SetObjectToLoading。读完成回调只置 m_Finished = true(注释提到可在此处顺带放置但未做)src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:2228-2239。

AllocateMemory src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:1647-1969:资源文件用 pgRscBuilder::GenerateMap 生成分块映射;非资源文件用单个虚拟块。模块使用额外内存时,RequestExtraMemory 生成的块与加载块合并为“加载虚拟 + 额外虚拟 + 加载物理 + 额外物理”的分配映射 src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:1708-1769。然后 StreamingAllocate,失败时区分三种情况:游戏回调判定“不值得腾空间”(WasLastAllocationAborted,churn 保护)直接返回;内存不足 → EMERGENCY_OUT_OF_MEMORY;碎片 → EMERGENCY_FRAGMENTED_MEMORY(非 FINAL 首次时打印详细内存报告)src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:1790-1932。PC 资源缓存构建另有 HandleOutOfMemory 分支 src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:1823-1828。

ProcessFile / ConvertToObject src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:2262-2381、src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:2383-2587:

  • CANCELED 文件:释放分配映射、RemoveDependentCountsAndUnrequest、释放档案引用、槽置 IDLE。
  • 否则 ConvertToObject:关闭文件 → 检查依赖都已加载 → ReceiveExtraMemory 移交额外块 → 若模块 CanPlaceAsynchronously 且未加 -nomtplacement:PlaceAsynchronously 并加入 m_AsyncPlacements 后返回(稍后由 ProcessAsyncFiles 收尾);否则资源/可异步模块在主线程 PlaceResource + PerformMainThreadPlacement(即 SetResource),其他模块 Load(ptr,size)。成功 → MarkPlacementComplete;失败 → 释放内存 + SetObjectToNotLoadedFromLoading。
  • MarkPlacementComplete:SetObjectToLoaded;临时内存模块释放加载映射;可碎片整理且未标 DONT_DEFRAG 的资源登记到碎片整理器 src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:2608-2642。

放置线程 ResourcePlacementFunc:从队列取请求(NULL 表示退出),持 DefragCs 锁调用模块 PlaceResource,然后置 file->m_IsPlaced = true;RESOURCE_HEADER 构建下对纹理字典做“把头部挪到 header 堆”的优化 src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:2973-3109。线程在 Init 中以 sysIpcMinThreadStackSize*4、PRIO_BELOW_NORMAL 创建(-nomtplacement 时不创建)src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:2729-2758。RequestAsyncPlacement 给对象置 INTERNAL_PLACING,并在“延迟模式”下暂存到数组 src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:3111-3131。

主线程收尾 ProcessAsyncFiles:遍历 m_AsyncPlacements,对 m_IsPlaced 的文件依次 PerformMainThreadPlacement(清 PLACING、SetResource)→ MarkPlacementComplete → FinalizePlacement(槽回 IDLE、挂起数-1);若期间被取消,则在可删时 RemoveObject 并 RemoveDependentCountsAndUnrequest(取消时为保护放置线程而额外加过依赖计数,见 CancelRequest src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:1011-1030)src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:297-416。非 FINAL 下单次耗时超过阈值(PC 32ms,其他 2000ms)打印 STALL 报告 src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:398-412。

4.3 帧内调用点与线程#

时机调用线程证据
CStreaming::Update 早段strStreamingEngine::PreLoadedUpdate()(BANK -aggressivecleanup 时清空 LRU)→ GetLoader().ProcessAsyncFiles()(时间条名 "Synchronous resource placement")主线程src/dev_ng/game/streaming/streaming.cpp:654-657、src/dev_ng/rage/framework/src/streaming/streamingengine.cpp:427-437
CStreaming::Update 后段strStreamingEngine::Update():开启延迟放置 → LoadRequestedObjects → PurgeRequestList(忽略 DONTDELETE 掩码/FORCE_LOAD/load scene 期间的 LOADSCENE)循环 → 加载器/分配器/信息/档案管理器 Update主线程src/dev_ng/game/streaming/streaming.cpp:800、src/dev_ng/rage/framework/src/streaming/streamingengine.cpp:440-498
CStreaming 所在的 "Streaming" 更新组之后(中间隔若干资产更新组)、CScene::Update 之前SubmitDeferredAsyncPlacementRequests:关闭延迟并把暂存请求推给放置线程(注释:减少流式时间段内主线程与放置线程的锁竞争)主线程src/dev_ng/game/Core/game.cpp:2760-2767、src/dev_ng/game/Core/game.cpp:2824、src/dev_ng/rage/framework/src/streaming/streamingengine.cpp:446-449、src/dev_ng/rage/framework/src/streaming/streamingengine.cpp:507-513
渲染线程帧尾附近strStreamingEngine::GetDefragmentation()->Update()(仅 USE_DEFRAGMENTATION,本快照中为 0)渲染线程src/dev_ng/game/renderer/renderThread.cpp:412-430
后台ResourcePlacementThread 执行 PlaceResource放置线程src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:2973-3108
pgStreamer 读完成SignalCallback 置 m_Finished流式 I/O 线程(推断:回调线程由 pgStreamer 决定,未在本目录核实)src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:2228-2239

几乎所有状态变更函数都断言 sysThreadType::IsUpdateThread()(RequestObject、RemoveObject、SetStatus、AddRequest、IssueRequest、StreamingAllocate、AddRef/RemoveRef 等)src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:985、src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:1346、src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:607、src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:1974、src/dev_ng/rage/framework/src/streaming/streamingallocator.cpp:656——流式引擎不是线程安全的,放置线程只做 PlaceResource,与碎片整理通过 DefragCs 互斥。

4.4 阻塞加载#

LoadAllRequestedObjects(bPriorityOnly):关闭延迟放置并提交暂存 → Flush → 循环 LoadRequestedObjects + ProcessAsyncFiles 直到全部(或全部优先)请求完成;期间按间隔调用 keep-alive 回调(默认 50ms,用于加载画面);若上次分配因碎片失败且渲染线程使用自定义渲染函数,则锁渲染互斥量、以 DEFRAG_PAUSE 模式在主线程跑一次碎片整理(紧急 defrag);BANK 下超过 60 秒放弃并打印请求/加载列表 src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:679-792、src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:2733-2734。可重入会 assert src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:420-425。


5. 淘汰与清理#

机制行为证据
LRU 已加载表新加载对象追加到表尾;DeleteNextLeastUsedObject 从表头迭代,跳过忽略位/有依赖者/有引用者、跳过指定模块,遇到“上一帧最后加载”的终止项就停止;命中后把对象及其递归依赖中可删者一并 RemoveObjectsrc/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:868-890、src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:3882-3959、src/dev_ng/rage/framework/src/streaming/streamingmodule.cpp:20-58
持久表DONTDELETE 类标志或有依赖者的已加载对象不在 LRU 表中,不会被 LRU 淘汰src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:2074-2081
请求清理每帧 PurgeRequestList(ignore, true) 删除未加忽略位、无依赖者的 LOADREQUESTED 对象(即仅设置普通请求、未 FORCE_LOAD 的请求只活一帧,需每帧重新请求)src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:3789-3836、src/dev_ng/rage/framework/src/streaming/streamingengine.cpp:455-456
会话结束ShutdownSession:Flush、中止碎片整理、两轮 purge(先保留依赖再清依赖),assert 构建下列出残留请求src/dev_ng/rage/framework/src/streaming/streamingengine.cpp:345-419
腾空间StreamingAllocate 前调用游戏 MakeSpaceForMap(map, index, true),具体策略在 CStreamingCleanupsrc/dev_ng/rage/framework/src/streaming/streamingallocator.cpp:654-704、src/dev_ng/game/streaming/streamingcleanup.cpp:1568-1600

“普通请求只活一帧”是推断自 Update 每帧以忽略 DONTDELETE 掩码|FORCE_LOAD 调用 PurgeRequestList;调用方若想跨帧保持请求,需要 FORCE_LOAD/DONTDELETE(strRequest 默认加 FORCE_LOAD,见 §8.1)或每帧重复请求(如 fwBoxStreamer 用 DONTDELETE,见 scene-graph-and-visibility.md §5.3)。


6. RPF 档案管理:strPackfileManager#

  • strStreamingFile 记录一个档案:名字、设备、句柄、原始头数据、fiCollection*、对其内文件的挂起请求计数 m_requestCount、是否已初始化、锁定/用户锁定、需重置、启用、覆盖(overlay)/补丁、是否完整加载、是否含加密数据、头大小、最后使用时间、在 HDD/ODD 上等 src/dev_ng/rage/framework/src/streaming/packfilemanager.h:25-117。
  • 档案本身是 "Archive" 模块(PI_ARCHIVE_FILE_ID=0,条目数 = 档案数 + 5)中的流式对象:Load → OnStreamIn,Remove → UnloadRpf,GetNumRefs = 已初始化时的 m_requestCount src/dev_ng/rage/framework/src/streaming/packfilemanager.cpp:63-171、src/dev_ng/rage/framework/src/streaming/streamingengine.cpp:120、src/dev_ng/rage/framework/src/streaming/streamingengine.cpp:133。因此“RPF 头”可像普通资源一样被请求、驻留与卸载;RPF 内文件请求会隐式请求并引用其档案(§3.4)。
  • LoadRpf:可从 RPF 缓存文件恢复条目列表,否则初始化 fiPackfile 并对每个非目录条目调用 RegisterObject(fullName, handle, imgIndex, …);记录是否含加密条目(含加密数据时保留名字堆);最后通知游戏 PostLoadRpf src/dev_ng/rage/framework/src/streaming/packfilemanager.cpp:1115-1253。加密的具体机制属于 ../02-rage-core/file-system-and-rpf.md 与安全页,此处不展开。
  • RegisterObject:按扩展名找模块(找不到且该扩展需要模块时报错)、名字长度 ≤ 256、非 FINAL 下校验资源版本(不匹配时 Quitf 或报错)、module->Register(去扩展名的名字),并处理同名文件在多个档案中的覆盖(overlay/patch 表)src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:4193-4335。
  • 档案驱逐:Update 每帧调用 UnlockLRUPackfile(false),但该函数在非 PS3/360 平台(或 -nocacherpf)直接返回 -1 src/dev_ng/rage/framework/src/streaming/packfilemanager.cpp:256-259、src/dev_ng/rage/framework/src/streaming/packfilemanager.cpp:1397-1403。即 PC/新主机上已锁定的 RPF 头从不按 LRU 解锁;RESIDENT_ARCHIVE_SOFT_LIMIT=256 只对旧主机有意义 src/dev_ng/rage/framework/src/streaming/packfilemanager.cpp:50。
  • 游戏侧通过 AddImageToList 挂载档案(参数说明见头文件注释)src/dev_ng/rage/framework/src/streaming/packfilemanager.h:150-159;InitLevelWithMapUnloaded 调用 LoadImageDirectories 读取档案目录 src/dev_ng/rage/framework/src/streaming/streamingengine.cpp:260-269。

7. 内存:分配器与碎片整理#

7.1 strStreamingAllocator#

strStreamingAllocator 派生自 sysMemAllocator,以 g_strStreamingInterface 提供的虚拟/物理预算 Init src/dev_ng/rage/framework/src/streaming/streamingallocator.h:58-100、src/dev_ng/rage/framework/src/streaming/streamingengine.cpp:129。游戏从 gameconfig 的 CConfigStreamingEngine 读预算:旧主机单位 KB、PC/新主机单位 MB(左移 10/20)src/dev_ng/game/streaming/streaming.cpp:296-314。StreamingAllocate(map, index):先 MakeSpaceForMap(真正的分配发生在游戏回调中——推断,依据是本函数只做 user data 标记与已用登记,未见直接分配),成功后给每块写入 user data(编码 strIndex,用于内存追踪与反查归属)并 AddToMemoryUsed src/dev_ng/rage/framework/src/streaming/streamingallocator.cpp:654-704。ONE_STREAMING_HEAP 在 PS3/PC 且 FREE_PHYSICAL_RESOURCES 时为 0(虚拟/物理两个堆),否则为 1 src/dev_ng/rage/framework/src/streaming/streamingdefs.h:31。

7.2 strDefragmentation#

USE_DEFRAGMENTATION = ENABLE_KNOWN_REFERENCES && (__CONSOLE || (RSG_PC && !FREE_PHYSICAL_RESOURCES)) src/dev_ng/rage/framework/src/streaming/streamingdefs.h:52;-nodefrag 在初始化后立即禁用 src/dev_ng/rage/framework/src/streaming/streamingengine.cpp:137-144。

本快照中碎片整理被编译关闭。 paging/knownrefpool_config.h 把 ENABLE_KNOWN_REFERENCES 定义为 0,并令 ENABLE_DEFRAGMENTATION = ENABLE_KNOWN_REFERENCES && 1 src/dev_ng/rage/base/src/paging/knownrefpool_config.h:1-2,因此上式在所有平台上都为 0,渲染线程里 #if USE_DEFRAGMENTATION 包裹的 GetDefragmentation()->Update() src/dev_ng/game/renderer/renderThread.cpp:412-430 不会被编译。下文描述的是该机制在开启时的设计,用于理解代码;当前构建的资源堆不做运行时整理(另见 paging-and-resources.md)。

  • 模式:DEFRAG_NORMAL(增量)、DEFRAG_PAUSE(每次 20ms)、DEFRAG_CUTSCENE_INIT(20ms)、DEFRAG_CUTSCENE_RUN(5ms)src/dev_ng/rage/framework/src/streaming/defragmentation.h:34-40、src/dev_ng/rage/framework/src/streaming/defragmentation.cpp:574-591。
  • 每个被整理对象是一个 subject,状态 Idle → CopyPhysical/Copy → Place → (CheckCopyPhysical) → WaitOneFrame → Finalise;同时最多 MAX_SUBJECTS(PPU 24,其他 16)个 src/dev_ng/rage/framework/src/streaming/defragmentation.h:109-141。
  • Update 先 TryLock(m_DefragLock),失败(放置线程正在 PlaceResource)则本帧跳过 src/dev_ng/rage/framework/src/streaming/defragmentation.cpp:551-558;无 subject 时 DefragmentAllocators() 选新目标 src/dev_ng/rage/framework/src/streaming/defragmentation.cpp:560-561。
  • Place 阶段:pgBase::PatchAllReferences(map) 修正所有已知引用 → module->Defragment(index, map, flush) 在新地址重新构造资源 → 非纯物理对象等待一帧再 Finalise(推断:等渲染线程不再使用旧地址)src/dev_ng/rage/framework/src/streaming/defragmentation.cpp:954-1049。
  • 资源由 MarkPlacementComplete 登记、RemoveObject 注销;正在整理的对象被删除时会 Cancel 并(可能)阻塞等待 GPU 拷贝完成 src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:2636-2641、src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:1459-1474、src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:1511-1514。

8. 端到端时序:请求一个模型到可用#

以“游戏代码请求一个 drawable 型模型(其 drawable 依赖一个 TXD)”为例,所有文件位于同一 RPF,且该 RPF 头已驻留。

图中证据(按顺序):

  • 请求与依赖递归、只传优先位 src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:1125-1259;模型依赖 src/dev_ng/game/modelinfo/ModelInfo.cpp:224-232、src/dev_ng/rage/framework/src/entity/archetypestreamingmodule.cpp:81-87;drawable 依赖 TXD src/dev_ng/rage/framework/src/fwscene/stores/drawablestore.cpp:210-219;AddRequest 对 dummy 放入 m_dummyRequestList、真实文件放入排序数组 src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:674-688;dummy 依赖满足时立即完成 src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:1307-1318。
  • 每帧加载、候选只取依赖已 LOADED 者 src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:2787-2809、src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:2553-2582。
  • 发起:src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:1557-1564、src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:2041、src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:2102-2110、src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:2180;分配回调 src/dev_ng/rage/framework/src/streaming/streamingallocator.cpp:666。
  • 完成与转换:src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:2228-2239、src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:1318-1342、src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:2486-2492;资源存储走异步放置 src/dev_ng/rage/framework/src/fwtl/assetstore.h:1324-1327;延迟提交 src/dev_ng/rage/framework/src/streaming/streamingengine.cpp:449、src/dev_ng/rage/framework/src/streaming/streamingengine.cpp:507-513;放置线程 src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:3082-3105;主线程收尾 src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:323-348、src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:2589-2642。
  • dummy 完成:src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:2283-2336、src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:4606-4645;游戏模型模块 Load src/dev_ng/game/modelinfo/ModelInfo.cpp:436-462。
  • 调用方加引用:src/dev_ng/rage/framework/src/streaming/streamingrequest.cpp:100-132。

延迟要点(由上推断):同一 RPF 内的依赖链至少需要“每层一次读 + 一次放置收尾”,而异步放置的收尾发生在下一帧 CStreaming::Update 早段的 ProcessAsyncFiles,因为放置请求在本帧 strStreamingEngine::Update 之后才提交(src/dev_ng/game/Core/game.cpp:2824)。dummy 模型在依赖全部 LOADED 后,于下一次 GetNextFilesOnCdNew 的 dummy 扫描中被完成。

8.1 strRequest:调用方的 RAII 句柄#

头注释给出用法:在类中放一个 strRequest,Request(id, moduleId) 后轮询 HasLoaded(),不用时 Release();析构自动释放 src/dev_ng/rage/framework/src/streaming/streamingrequest.h:14-41。实现要点:

  • Request 若目标改变则先 Release,并强制附加 STRFLAG_FORCE_LOAD(因此请求不会被每帧 purge)src/dev_ng/rage/framework/src/streaming/streamingrequest.cpp:23-35。
  • HasLoaded 未加载时每次调用都 StreamingRequest;加载后补设 DONTDELETE 类标志并 AddRef(REF_SCRIPT),只加一次 src/dev_ng/rage/framework/src/streaming/streamingrequest.cpp:100-132。
  • Release 对已加载者 RemoveRef(REF_SCRIPT);DelayedRelease 先 AddRef(REF_RENDER) 并交给 gDrawListMgr 在若干帧后释放(避免渲染线程仍在使用)src/dev_ng/rage/framework/src/streaming/streamingrequest.cpp:60-81。
  • 拷贝构造会立即 HasLoaded(),赋值等价于重新 Request src/dev_ng/rage/framework/src/streaming/streamingrequest.h:74-85。m_flags 只有 8 位、m_moduleId 7 位(INVALID_MODULE=0x7F)src/dev_ng/rage/framework/src/streaming/streamingrequest.h:48-50、src/dev_ng/rage/framework/src/streaming/streamingrequest.h:93-96。

9. 辅助子系统#

子系统作用构建/平台证据
strStreamingLive热重载:监视 preview/ 文件夹(或 -previewfolder),把同名文件链接到已有 strIndex,最多 250 个LIVE_STREAMING = !__FINALsrc/dev_ng/rage/framework/src/streaming/streamingdefs.h:51、src/dev_ng/rage/framework/src/streaming/streaminglive.h:31-60、src/dev_ng/rage/framework/src/streaming/streamingengine.cpp:230-257
strCacheLoader启动缓存:若模块时间戳一致,直接从缓存文件读出部分资源“子数据”(如包围盒)而不加载整个资源;最多 6 个缓存模块全构建src/dev_ng/rage/framework/src/streaming/CacheLoader.h:15-45
strRequestRecorder录制/回放请求序列(strRecordedRequest 等,parser 反射)主要非 FINALsrc/dev_ng/rage/framework/src/streaming/requestrecorder.h:23-60、src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:1002-1005
fiInstallerDevicePS3 安装到硬盘的设备接口PS3src/dev_ng/rage/framework/src/streaming/streaminginstall.h:15-52
strIndexDebugManager记录每个 strIndex 的来源类型/名字用于调试!__FINALsrc/dev_ng/rage/framework/src/streaming/strindexdebugmanager.h:21-25
strStreamingVisualize通过 TCP 把请求/状态/读取/实体等事件发给外部 StreamingVisualize 工具(协议版本 22,51 种包类型);-streamingvisualize 启动时连接STREAMING_VISUALIZE = PGSTREAMER_DEBUG && !__FINAL && !__RESOURCECOMPILERsrc/dev_ng/rage/framework/src/streaming/streamingvisualize.h:15、src/dev_ng/rage/framework/src/streaming/streamingvisualize.h:36-94、src/dev_ng/rage/framework/src/streaming/streamingvisualize.cpp:55、src/dev_ng/rage/framework/src/streaming/streamingvisualize.cpp:399-456
磁盘缓存UseDiskCache() 恒返回 false(注释:为统一 360 行为)—src/dev_ng/rage/framework/src/streaming/streamingengine.cpp:75-85

10. 平台与构建差异汇总#

条件影响证据
RSG_ORBISMAX_PENDING_KB 32768(其他 8192)src/dev_ng/rage/framework/src/streaming/streamingdefs.h:119-123
__PPU / PC + FREE_PHYSICAL_RESOURCES两个流式堆(ONE_STREAMING_HEAP=0)src/dev_ng/rage/framework/src/streaming/streamingdefs.h:31
主机 / PC 非 FREE_PHYSICAL_RESOURCES设计上启用碎片整理,但还需 ENABLE_KNOWN_REFERENCES;该宏在本快照中为 0,故实际关闭(见 §7.2)src/dev_ng/rage/framework/src/streaming/streamingdefs.h:52
PS3 / 360唯一会按 LRU 解锁 RPF 头的平台;碎片整理 subject 上限 PPU 24src/dev_ng/rage/framework/src/streaming/packfilemanager.cpp:1400、src/dev_ng/rage/framework/src/streaming/defragmentation.h:140
旧主机 vs 新平台流式预算单位 KB vs MBsrc/dev_ng/game/streaming/streaming.cpp:301-303
光驱设备请求按 LSN 排序;读取使用 UNCACHEDsrc/dev_ng/rage/framework/src/streaming/streaminginfo.h:700、src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:1597-1600
__DEV引用计数按种类细分src/dev_ng/rage/framework/src/fwtl/assetstore.h:32
!__FINAL热重载、请求录制、资源版本校验、紧急内存报告、strIndexDebugManagersrc/dev_ng/rage/framework/src/streaming/streamingdefs.h:51、src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:4245-4272
__FINAL || __NO_OUTPUT对象名只存哈希(atHashValue)src/dev_ng/rage/framework/src/streaming/streamingmodule.h:34-40
命令行-nomtplacement、-nodefrag、-aggressivecleanup、-nocacherpf、-noarea51、-sortonhdd、-fairhddscheduler、-streamingvisualizesrc/dev_ng/rage/framework/src/streaming/streamingloader.cpp:65、src/dev_ng/rage/framework/src/streaming/streamingengine.cpp:72-73、src/dev_ng/rage/framework/src/streaming/packfilemanager.cpp:53、src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:339-342

g_AREA51 默认开启(-noarea51 关闭)src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:339:开启时每帧候选链长度降为 1、强制按 LSN 排序 HDD 请求、依赖未满足的已读文件推回队尾、依赖扫描每帧只做一次 src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:451-460、src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:1321-1339、src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:2275-2279。这是一种调度策略开关,名称本身没有进一步注释说明含义。

11. 约束与边界行为#

  • 线程:除 PlaceResource(放置线程)与 SignalCallback 外,所有入口必须在更新线程调用(assert)。放置线程与碎片整理靠 DefragCs 互斥;碎片整理拿不到锁就跳过本帧。
  • 请求寿命:普通请求若无 FORCE_LOAD/DONTDELETE 类标志,会被每帧 PurgeRequestList 清除(无依赖者时)。
  • 降级禁止:已请求对象不能从优先降为普通;LOADED 时不得直接改 DONTDELETE 掩码。
  • 依赖上限 100;递归 RemoveDependentCountsAndUnrequest 深度 > 8 告警。
  • 容量失败:请求池满 → RequestObject 返回 false;已加载池满 → Quitf;排序数组满 → Quitf;模块数 > 30 → assert;全局索引越界 → Quitf。
  • 刚加载对象保护:LRU 删除不会越过“上一帧最后加载”的终止项。
  • 取消正在放置的对象:RemoveObject 返回 true 但对象仍为 LOADING,放置完成后才真正删除;期间其依赖被额外持有。
  • dummy 不能被发起读取(RequestStreamFiles 断言)src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:1453。

12. 未决问题 / 证据不足#

  • CStreamingCleanup::MakeSpaceForMap 内部如何分配/淘汰(churn 保护、模块优先级)属于游戏层,未在本页展开;本页对“真实分配发生在回调内”的判断是推断。
  • strDefragmentation::DefragmentAllocators 的目标选择、ProcessSubjectsIncremental 的预算,以及 GPU 拷贝路径(eStateCopyPhysical)未逐行核对。
  • strPackfileManager 的档案锁定/用户锁定、overlay/patch 卸载回滚、RPF 缓存文件格式未展开。
  • pgStreamer 的读线程模型与 SignalCallback 所在线程未在本目录核实(见 ../02-rage-core/file-system-and-rpf.md)。
  • g_AREA51 名称的设计意图无注释;GetNextFilesOnCdNew 中 HDD 公平调度 -fairhddscheduler 的效果只从代码推断。