流式引擎:rage/framework/src/streaming
本页解释 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;本页只讲框架机制。
背景与相关页面#
- 资源格式、
datResourceMap、pgBase、pgRscBuilder、资源版本:见 ../02-rage-core/paging-and-resources.md。RPF 档案格式与fiPackfile/pgStreamer:见 ../02-rage-core/file-system-and-rpf.md。 - 资源堆/buddy 分配器、内存预算:见 ../01-runtime/memory-and-pools.md。渲染线程与主线程同步:见 ../01-runtime/threading-and-render-thread.md。
- 原型(
fwArchetype)作为 dummy 流式对象:见 entity-and-archetype.md。IMAP 存储与fwBoxStreamer:见 scene-graph-and-visibility.md。fwConfigManager池尺寸:见 framework-systems.md。 - 游戏侧
CStreaming、CStreamingCleanup::MakeSpaceForMap、CStreamingRequestList:见 ../05-world/game-streaming-and-scene-streamer.md。调试工具 StreamingVisualize 客户端:见 ../24-toolchain/rage-tools-and-rag.md。
相关源码文件#
| 文件 | 内容 |
|---|---|
streaming/streamingdefs.h | 配置宏、请求/内部标志、引用种类、容量常量、strIndex/strLocalIndex |
streaming/streamingengine.h/.cpp | strStreamingEngine(静态门面)、strStreamingInterface(游戏回调) |
streaming/streaminginfo.h/.cpp | strStreamingInfo、strStreamingInfoManager(请求、状态机、依赖、淘汰、注册) |
streaming/streamingmodule.h/.cpp、streamingmodulemgr.h/.cpp | 模块基类与模块管理器(索引区间、扩展名映射) |
streaming/streamingloader.h/.cpp | strStreamingLoader、strStreamingLoaderManager、放置线程 |
streaming/packfilemanager.h/.cpp | strPackfileManager、strPackfileStreamingModule("Archive" 模块) |
streaming/streamingallocator.h/.cpp、defragmentation.h/.cpp | 资源分配与碎片整理 |
streaming/streamingrequest.h/.cpp | strRequest/strRequestArray:RAII 风格的请求句柄 |
streaming/streaminglive.*、CacheLoader.*、requestrecorder.*(+.psc)、streaminginstall*、strindexdebugmanager.*、streamingvisualize.* | 热重载、缓存加载、请求录制、PS3 安装、调试 |
fwtl/assetstore.h | fwAssetStore/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为 0src/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_indexOffsetsrc/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 / GetResource | NULL | 取对象指针 / 释放内存时取数据指针 / 碎片整理取 pgBase | src/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 / GetNumRefs | GetNumRefs 默认返回 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 / PlaceAsynchronously | false / 空 | 是否在放置线程上放置 | src/dev_ng/rage/framework/src/streaming/streamingmodule.h:340-345 |
ModifyHierarchyStatus | false | 层次化启用/禁用(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同值);否则一个intsrc/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”)会直接Removesrc/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.xmlsrc/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:
| 配置键 | 用途 | 默认 |
|---|---|---|
MaxLoadedInfo | strLoadedInfo 池 | 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+CompleteDummyRequestsrc/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:1109-1320。IssueRequest成功发起读取后SetObjectToLoading,它把请求表项移到加载中表并清PRIORITY_LOADsrc/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。- 失败路径
SetObjectToNotLoadedFromLoadingsrc/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:
| 位 | 标志 | 含义 |
|---|---|---|
| 0 | STRFLAG_DONTDELETE | 加载后不可删除,直到显式清除 |
| 1 | STRFLAG_FORCE_LOAD | 请求保持到加载完成(不被 purge) |
| 2 | STRFLAG_PRIORITY_LOAD | 优先队列 |
| 3 | STRFLAG_LOADSCENE | 属于 load scene;load scene 期间不被 purge |
| 4–7 | MISSION_/CUTSCENE_/INTERIOR_/ZONEDASSET_REQUIRED | 各子系统的“需要”位 |
| 8–14 | INTERNAL_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 / fragment | fwDrawableStore / 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 |
| TXD | fwTxdStore | 父 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()==0src/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_Enabled | true | 关闭时每设备同时只允许 1 个挂起请求 |
ms_MaxPendingRequestThreshold | 30 | 每设备最多挂起文件数(含等待放置) |
ms_ExtremeStreaming_MaxPendingKb | MAX_PENDING_KB:Orbis 32768,其他 8192 | 挂起数据量上限(KB) |
ms_ExtremeStreaming_MaxConvertMs | MAX_CONVERT_MS = 8 | 每帧转换/发起请求的时间预算 |
ms_LsnTolerance[2] | 0x4000000 | 连续请求间允许的 LSN 间隙 |
ms_MaxChainedRequests | 24(AREA51 时每帧改为 1) | 每次取候选的最大数 |
MAX_LOAD_PER_FRAME | 4 | RequestStreamFiles 单批最多新发请求 |
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:
ProcessStreamFiles():从队列头取m_Finished的文件ProcessFile,超过MaxConvertMs即停;AREA51 模式下依赖未满足的文件被推回队尾src/dev_ng/rage/framework/src/streaming/streamingloader.cpp:1308-1357。- 挂起数 ≥ 阈值则提前返回。
- 循环:
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 从表头迭代,跳过忽略位/有依赖者/有引用者、跳过指定模块,遇到“上一帧最后加载”的终止项就停止;命中后把对象及其递归依赖中可删者一并 RemoveObject | src/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),具体策略在 CStreamingCleanup | src/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_requestCountsrc/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, …);记录是否含加密条目(含加密数据时保留名字堆);最后通知游戏PostLoadRpfsrc/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)直接返回 -1src/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 && 1src/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 依赖 TXDsrc/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;游戏模型模块Loadsrc/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(),赋值等价于重新Requestsrc/dev_ng/rage/framework/src/streaming/streamingrequest.h:74-85。m_flags只有 8 位、m_moduleId7 位(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 = !__FINAL | src/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 反射) | 主要非 FINAL | src/dev_ng/rage/framework/src/streaming/requestrecorder.h:23-60、src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:1002-1005 |
fiInstallerDevice | PS3 安装到硬盘的设备接口 | PS3 | src/dev_ng/rage/framework/src/streaming/streaminginstall.h:15-52 |
strIndexDebugManager | 记录每个 strIndex 的来源类型/名字用于调试 | !__FINAL | src/dev_ng/rage/framework/src/streaming/strindexdebugmanager.h:21-25 |
strStreamingVisualize | 通过 TCP 把请求/状态/读取/实体等事件发给外部 StreamingVisualize 工具(协议版本 22,51 种包类型);-streamingvisualize 启动时连接 | STREAMING_VISUALIZE = PGSTREAMER_DEBUG && !__FINAL && !__RESOURCECOMPILER | src/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_ORBIS | MAX_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 24 | src/dev_ng/rage/framework/src/streaming/packfilemanager.cpp:1400、src/dev_ng/rage/framework/src/streaming/defragmentation.h:140 |
| 旧主机 vs 新平台 | 流式预算单位 KB vs MB | src/dev_ng/game/streaming/streaming.cpp:301-303 |
| 光驱设备 | 请求按 LSN 排序;读取使用 UNCACHED | src/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 | 热重载、请求录制、资源版本校验、紧急内存报告、strIndexDebugManager | src/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、-streamingvisualize | src/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的效果只从代码推断。