CD Code Digest Hub /GTA V · v2

实体与原型:fwEntity / fwArchetype / 扩展机制 / fwDecorator

74 分钟阅读 11 个章节 2 张图

本页解释 RAGE Framework 层(rage/framework/src/entity 与 fwdecorator)如何表示“世界中的一个东西”: fwEntity 是所有世界实体(建筑、车、人、物体、灯光……)的公共基类,fwArchetype 是被许多实体共享的“模型定义”,二者通过 fwModelId/流式槽位相连;扩展(fwExtension)和装饰器(fwDecorator)让上层在不修改基类的前提下往实体上挂数据。理解这一层是读懂 CEntity 类层次、地图加载、流式依赖与脚本数据挂载的前提。

背景与相关页面#

相关源码文件#

文件内容
rage/framework/src/entity/entity.h/.cppfwEntity 定义、标志位、变换、物理/骨骼/LOD 访问器、从地图定义初始化
rage/framework/src/entity/extensiblebase.h、extensionlist.h/.cppfwExtensibleBase、fwExtension、fwExtensionList、扩展工厂 typedef
rage/framework/src/entity/transform.h/.inl/.cppfwTransform 族(矩阵/四元数/简化/恒等变换)与其分页池
rage/framework/src/entity/dynamicentitycomponent.h/.cppfwDynamicEntityComponent:动态实体的惰性附加组件
rage/framework/src/entity/attachmententityextension.h/.cppfwAttachmentEntityExtension:附着状态机与约束
rage/framework/src/entity/drawdata.h/.cppfwDrawData、fwCustomShaderEffect、ActiveEntity
rage/framework/src/entity/sceneupdate.h/.cppfwSceneUpdate:按位注册的逐实体更新回调
rage/framework/src/entity/archetype.h/.cpp、dynamicarchetype.h/.cppfwArchetype、fwDynamicArchetypeComponent
rage/framework/src/entity/archetypemanager.h/.cpp、factory.hfwArchetypeManager、fwModelId、工厂与按 .ityp 分仓存储
rage/framework/src/entity/archetypestreamingmodule.h/.cppfwArchetypeStreamingModule("ModelInfo" 流式模块)
rage/framework/src/entity/altskeletonextension.*、camerarelativeextension.*两个小型实体扩展
rage/framework/src/fwdecorator/*fwDecorator、fwDecoratorExtension、fwDecoratorInterface
rage/framework/src/fwscene/lod/LodTypes.hfwLodData(嵌在 fwEntity 内)

1. 类关系总览#

图中证据:fwExtensibleBase : fwRefAwareBase 及其扩展表 src/dev_ng/rage/framework/src/entity/extensiblebase.h:22-41; fwEntity : fwExtensibleBase 及成员 src/dev_ng/rage/framework/src/entity/entity.h:84、 src/dev_ng/rage/framework/src/entity/entity.h:540-576;动态组件持有附着扩展 src/dev_ng/rage/framework/src/entity/dynamicentitycomponent.h:105-122; fwArchetype 自带扩展表与动态组件指针 src/dev_ng/rage/framework/src/entity/archetype.h:268-296;管理器的池与映射 src/dev_ng/rage/framework/src/entity/archetypemanager.h:271-285;两个扩展子类 src/dev_ng/rage/framework/src/fwdecorator/decoratorExtension.h:26、 src/dev_ng/rage/framework/src/entity/sceneupdate.h:19-32。

注意 fwArchetype 不是 fwExtensibleBase:它直接派生自 datBase 并自带一个 fwExtensionList src/dev_ng/rage/framework/src/entity/archetype.h:49、 src/dev_ng/rage/framework/src/entity/archetype.h:127-131,因此不能被 fwRegdRef 跟踪,也不能挂装饰器(装饰器 API 要求 fwExtensibleBase&)。


2. 扩展机制:fwExtensibleBase / fwExtension / fwExtensionList#

2.1 解决的问题#

几乎所有“可选的、只有部分实体需要”的数据(场景更新登记、装饰器、2D 特效、光源属性、游戏层的各种 CxxxExtension)都以扩展形式挂在实体或原型上,从而让 fwEntity 保持固定尺寸(见 §3.1 的编译期断言)。

2.2 结构与查找#

  • fwExtensibleBase 只多出一个 fwExtensionList m_extensionList;断言整个基类在 32 位下 16 字节、64 位下 32 字节 src/dev_ng/rage/framework/src/entity/extensiblebase.h:40-42。
  • 类型标识用 AUTOID:根类声明 EXTENSIONID_DECL_ROOT(虚函数 GetExtensionId()),子类用 EXTENSIONID_DECL(name, family) src/dev_ng/rage/framework/src/entity/extensionlist.h:26-37; fwExtension 根类的实现在 entity.cpp 中用 AUTOID_IMPL(fwExtension) 实例化 src/dev_ng/rage/framework/src/entity/entity.cpp:47。
  • fwExtensionList 是单链表 + 32 位位集:ID 小于 MAX_BIT_MANAGED_EXTENSIONS(32) 的扩展在位集里有一位, 查找时先查位集做快速否定,再遍历链表 src/dev_ng/rage/framework/src/entity/extensionlist.h:80-133、 src/dev_ng/rage/framework/src/entity/extensionlist.cpp:43-68。 ID ≥ 32 的扩展只能线性遍历;位集与链表不一致时会断言(“Inconsistent extension list bit mask”)。
  • 每个链表节点都用 rage_new LinkedListNode 单独分配(不走池) src/dev_ng/rage/framework/src/entity/extensionlist.cpp:19-31。
操作实现效果 / 约束
Add(ext)头插链表、置位断言更新线程;同 ID 重复添加会断言 extensionlist.cpp:19-31
Get(id) / GetExtension<T>()位集早退 + 遍历返回 static_cast 后的指针 extensiblebase.h:34-35
Unlink(ext)清位、摘链不 delete 扩展对象本身 extensionlist.cpp:83-115
Destroy(ext) / Destroy(id)摘链 + delete断言更新线程 extensionlist.cpp:72-150
DestroyAll()析构时调用逐个 delete 扩展与节点 extensionlist.cpp:13-17、extensionlist.cpp:153-169

边界:Unlink 之后对象生命周期由调用者负责——fwSceneUpdate 就是利用这一点让扩展对象驻留在自己的固定数组里(§3.10)。

2.3 扩展工厂与数据驱动创建#

extensionlist.h 为扩展定义了两种工厂:fwExtensionFactory<T>(按 .ityp/.imap 槽位分仓的动态存储)与 fwExtensionPoolFactory<T>(fwPool 存储),以及工厂管理器 fwExtensionManager = fwFactoryManager<fwExtension> src/dev_ng/rage/framework/src/entity/extensionlist.h:39-46。

从地图实例定义创建实体扩展的路径是 fwEntity::InitExtensionsFromDefinition:对 fwEntityDef::m_extensions 中每个 fwExtensionDef,取其 parser 结构名哈希 → fwMapData::GetExtensionFactory(hash) 得到工厂 ID → fwExtensionManager::GetFactory(id)->CreateBaseItem(mapDataDefIndex) → InitEntityExtensionFromDefinition → GetExtensionList().Add() src/dev_ng/rage/framework/src/entity/entity.cpp:445-469。哈希到工厂的关联由游戏层通过 fwMapData::AssociateExtensionDef 登记,重复登记会断言 src/dev_ng/rage/framework/src/fwscene/mapdata/mapdata.cpp:51-61。工厂返回 NULL(池满)时只断言、不中止("Likely pool is full"),实体会缺少该扩展继续存在 src/dev_ng/rage/framework/src/entity/entity.cpp:460-467。

原型侧的 2D 特效扩展另有一个工厂管理器 ms_2dEffectFactoryMgr src/dev_ng/rage/framework/src/entity/archetypemanager.h:268-269。

2.4 框架层内置扩展#

扩展用途证据
fwSceneUpdateExtension实体参与哪些场景更新回调(位掩码)sceneupdate.h:19-32
fwDecoratorExtension脚本/游戏挂载的键值装饰器链表decoratorExtension.h:26-118
fwAltSkeletonExtension替代骨骼(小型扩展)altskeletonextension.h:16-20
fwCameraRelativeExtension相机相对定位(小型扩展)camerarelativeextension.h:16-20

fwAttachmentEntityExtension 虽然名字叫 extension,但不在扩展链表里:它由 fwDynamicEntityComponent 直接持有指针 src/dev_ng/rage/framework/src/entity/dynamicentitycomponent.h:96-99。


3. fwEntity#

3.1 内存布局与编译开关#

entity.h 打开了两个关键开关:ENABLE_FORCE_MATRICES 1 与 ENABLE_MATRIX_MEMBER 1 src/dev_ng/rage/framework/src/entity/entity.h:38-39。后者使变换以 fwMatrixScaledTransform 值成员内嵌在实体中(而非指向池化变换对象的指针),因此编译期断言 fwEntity 在 32/64 位下都是 192 字节(RAGE_INSTANCED_TECH 时同样 192) src/dev_ng/rage/framework/src/entity/entity.h:584-596。成员旁注释里的“offset”仍是旧的 32 位/指针变换布局,与当前 192 字节布局不一致,应以断言为准。

成员类型作用证据
m_pArchetypefwArchetype*所属原型entity.h:542
m_typeu8实体类型(值由游戏层 ENTITY_TYPE_* 定义),SetType 会触发 UpdateRenderFlags()entity.h:165-167
m_protectedFlagsu8受保护标志(§3.2)entity.h:544
m_RenderFlagsu16渲染标志,低 10 位留给 RAGE,游戏位需左移 fwRenderFlag_USER_FLAG_SHIFTentity.h:157-162
m_baseFlagsu32基础标志(§3.2)entity.h:549
m_CurrentPhysicsInstphInst*当前物理实例entity.h:550
m_phaseVisibilityMaskfwFlags16渲染阶段可见性掩码(哪些 phase 子集可渲染此实体),构造时全置位entity.h:410-415、entity.cpp:81
m_visibilityTypeu16游戏定义的“种类”,渲染阶段可据此剔除entity.h:417-420
m_containerunion所属实体容器指针 或 “保留列表 ID”(§3.5)entity.h:556-560
m_pDrawHandlerfwDrawData*绘制句柄(drawable + 着色器实例)entity.h:562
m_DynamicEntityComponentfwDynamicEntityComponent*惰性动态组件(§3.6)entity.h:565
m_TransformfwMatrixScaledTransform变换(§3.3)entity.h:566-570
m_lodDatafwLodDataLOD 层次与 alpha(§3.4)entity.h:572
m_viewportInstancedRenderBitu8仅 RAGE_INSTANCED_TECHentity.h:573-575

PPUVIRTUAL 在 SPU 编译时展开为空:同一份头文件会被 SPU 作业(扫描/搜索)引用,所以部分虚函数在 SPU 上被去虚化 src/dev_ng/rage/framework/src/entity/entity.h:43-47。

3.2 标志位#

flags(m_baseFlags,必须放进 32 位) src/dev_ng/rage/framework/src/entity/entity.h:100-145:

位名称含义(据注释)
0IS_VISIBLE参与可见性(构造时默认置位)
1IS_SEARCHABLE参与世界搜索(构造时默认置位)
2–7HAS_OPAQUE/ALPHA/DECAL/CUTOUT/WATER/DISPL_ALPHA参与的绘制 pass
8 / 14HAS_PRERENDER / OK_TO_PRERENDER有预渲染效果 / 允许预渲染
9IS_LIGHT灯光实体(进入外景灯光容器)
10–13USE_SCREENDOOR、SUPPRESS_ALPHA、FORCE_ALPHA、RENDER_SMALL_SHADOW透明/阴影控制
15HAS_NON_WATER_REFLECTION_PROXY_CHILD 与 HAS_HYBRID_ALPHA同一位被两个名字共享(注释 SHARED(15))
16IS_DYNAMIC动态实体(进入外景动态容器)
17 / 18IS_FIXED / IS_FIXED_BY_NETWORK物理固定 / 网络克隆固定
19SPAWN_PHYS_ACTIVE加入世界时激活物理
20IS_TIMED_ENTITY使用时间段模型
21 / 22DONT_STREAM / LOW_PRIORITY_STREAM流式提示
23HAS_PHYSICS_DICT—
24REMOVE_FROM_WORLD下次处理时移除
25FORCED_ZERO_ALPHA被(脚本)强制 alpha=0
26 / 27DRAW_LAST / DRAW_FIRST_SORTED排序提示
28NO_INSTANCED_PHYS不实例化碰撞
29HAS_HD_TEX_DIST支持 HD 顶层 mip 流式
30PROTECT_ARCHETYPE实体对流式原型持有 .ityp 引用(§3.9)
31HAS_ALPHA_SHADOW—

protectedFlags(m_protectedFlags):IS_FIXED_UNTIL_COLLISION、USES_COLLISION(注释强调只能通过 CEntity::EnableCollision/DisableCollision 改)、HAS_SLOD_REF、HAS_FPV src/dev_ng/rage/framework/src/entity/entity.h:147-152。

在 __BANK 构建中,SetBaseFlag/ClearBaseFlag/AssignBaseFlag/AssignProtectedBaseFlag 都会回调虚函数 LogBaseFlagChange,供游戏层记录标志变化 src/dev_ng/rage/framework/src/entity/entity.h:200-210、 src/dev_ng/rage/framework/src/entity/entity.h:532。

3.3 变换存储#

fwTransform 族用一个“覆盖(overlay)”结构把类型码和位置偏移塞进对象第 12–15 字节(fwTransformOverlay::m_unused[3] 之后) src/dev_ng/rage/framework/src/entity/transform.h:174-188、 src/dev_ng/rage/framework/src/entity/transform.h:333-363,从而在没有 vtable 的情况下按类型分派。类型码:

类型值含义
TYPE_SIMPLE / TYPE_SIMPLE_SCALED0 / 2位置 + 航向(仅绕 Z)
TYPE_MATRIX / TYPE_MATRIX_SCALED1 / 3完整 3x4 矩阵(缩放存于列的 w 分量)
TYPE_IDENTITY4单例恒等变换
TYPE_QUATERNION / TYPE_QUATERNION_SCALED8 / 10四元数 + 位置

证据:src/dev_ng/rage/framework/src/entity/transform.h:196-209。非成员模式下这些变换类型来自分页池(FWTRANSFORM_USE_PAGED_POOLS 1、FWTRANSFORM_USE_RSCVIRT_FOR_PAGED_POOLS 1 → fwRscMemPagedPool,放在资源虚拟内存) src/dev_ng/rage/framework/src/entity/transform.h:22-44、 src/dev_ng/rage/framework/src/entity/transform.cpp:31-39;池满时以 ERR_GEN_ENT_TRANS 直接 Quitf src/dev_ng/rage/framework/src/entity/transform.h:50-72。

当前构建(ENABLE_MATRIX_MEMBER=1)的实际行为:

  • 构造时 m_Transform.SetIdentity()、缩放 (1,1) src/dev_ng/rage/framework/src/entity/entity.cpp:67-72。
  • fwMatrixScaledTransform 把 XY 缩放存于第 1 列 w、Z 缩放存于第 2 列 w src/dev_ng/rage/framework/src/entity/transform.h:538-542 (类注释写的是“XY 存于 col0 和 col1”,与访问器实现不一致,以访问器为准)。
  • InitTransformFromDefinition 的分支(ENABLE_MATRIX_MEMBER 下全部生成 Mat34V) src/dev_ng/rage/framework/src/entity/entity.cpp:174-368:
    1. 若原型有剪辑字典且 GetHasAnimation(),视为 dynamic;
    2. scaled = (scaleXY≠1 || scaleZ≠1) && 原型不是 DT_FRAGMENT,并断言缩放在 (0,4) 内 entity.cpp:185-189;
    3. 若 dynamic、或四元数 |x|/|y| > 0.05、或带 FLAG_FULLMATRIX 且 x/y 非零 → 用完整四元数构造矩阵 entity.cpp:197-212;
    4. 否则视为只有航向:由 acos(w) 与 z 的符号推出 heading,手工拼装列向量 entity.cpp:284-302。

注释指出“是否用完整矩阵”的判定在工具侧(静态碰撞烘焙)有一份副本,修改需要同步 entity.cpp:171-173。

  • SetTransform/SetMatrix/SetPosition/SetHeading 写入后,若有骨骼则重设骨骼父矩阵,并在 bUpdateGameWorld 时调用虚函数 UpdateWorldFromEntity()(游戏层据此更新世界表示) src/dev_ng/rage/framework/src/entity/entity.h:633-667、 src/dev_ng/rage/framework/src/entity/entity.h:689-757。 对处于 ATTACH_STATE_BASIC/WORLD 附着的实体直接改矩阵会断言(除非带 ATTACH_FLAG_DONT_ASSERT_ON_MATRIX_CHANGE 或正在 detach)。

推断:由于实体固定内嵌 64 字节矩阵,fwSimpleTransform/fwQuaternionTransform 及其池在 ENABLE_MATRIX_MEMBER=1 时只剩少数非实体用途; fwTransform::InitPools 仍会创建这些池 transform.cpp:61-65。未逐一核实所有其他调用者。

3.4 LOD 数据(fwLodData)#

fwLodData 以位域压缩在 8+ 字节内:LOD 父指针 m_pLod、16 位 m_lodDistance/m_childLodDistance、8 位 alpha、子数/已附子数、LOD 类型以及若干状态位 src/dev_ng/rage/framework/src/fwscene/lod/LodTypes.h:63-198。 LOD 深度枚举有 7 个值(HD、LOD、SLOD1–3、ORPHANHD、SLOD4) LodTypes.h:37-48,而宏 LODTYPES_MAX_NUM_LEVELS 仍写着 6 LodTypes.h:18(值不一致;实际数组按 LODTYPES_DEPTH_TOTAL 分配)。实体初始化时 m_lodDist>0 用实例值,否则回退到原型 GetLodDistanceUnscaled(),并断言 >1 src/dev_ng/rage/framework/src/entity/entity.cpp:480-486。 GetRootLod() 沿 m_pLod 走到根,孤立 HD 返回 NULL entity.cpp:706-715。 LOD 层次的建立(fwLodAttach)与淡入淡出语义归 ../05-world/map-data-and-lod.md。

3.5 容器归属:m_container#

实体要么属于某个 fwBaseEntityContainer(被世界表示管理),要么处于“保留列表(retaining list)”中;两者共用一个 union,最低位作为标签:置 1 表示存的是 (listId<<1)|1 src/dev_ng/rage/framework/src/entity/entity.h:440-451。容器在 AppendEntity 时写回该指针(见 scene-graph-and-visibility.md §2.6),析构时断言已清空 src/dev_ng/rage/framework/src/entity/entity.cpp:130。

3.6 动态组件 fwDynamicEntityComponent#

只有“动起来”的实体才需要骨骼、动画导演、动画速度、附着、碰撞体缓存等,因此这些被放到一个惰性创建的组件中: CreateDynamicComponentIfMissing() 首次调用时 rage_new src/dev_ng/rage/framework/src/entity/entity.h:819-827。组件来自 fwPool:FW_INSTANTIATE_CLASS_POOL_SPILLOVER(fwDynamicEntityComponent, 500, 0.15f, "fwDynamicEntityComponent")(尺寸可被 gameconfig 覆盖) src/dev_ng/rage/framework/src/entity/dynamicentitycomponent.cpp:13。

字段用途证据
m_vAnimatedVelocity、m_fAnimatedAngularVelocity动画提取的线/角速度dynamicentitycomponent.h:106-119
m_pEntityDesc动态实体在外景“动态容器”中的描述符地址(O(1) 删除/更新)dynamicentitycomponent.h:61-62
m_pSkeleton、m_pCreature、m_pTargetManager、m_pAnimDirector骨骼/动画生物/混合形状/动画导演dynamicentitycomponent.h:109-114
m_pAttachmentEntityExtension附着扩展(惰性)dynamicentitycomponent.cpp:78-94
m_pCollider缓存的 phCollider*(见 §3.7)dynamicentitycomponent.h:101-103
m_pNoCollisionEntity(fwRegdRef)、m_nNoCollisionFlags与指定实体临时不碰撞;PrepareNoCollisionReset/ResetNoCollision 跨帧清理dynamicentitycomponent.h:27-37、dynamicentitycomponent.h:142-171

CreateAnimDirector 要求已有骨骼,创建池化的 fwAnimDirectorPooledObject dynamicentitycomponent.cpp:54-66。 GetSkeleton() 优先从 fragInst 取骨骼,否则取组件骨骼 src/dev_ng/rage/framework/src/entity/entity.h:851-859。读骨骼矩阵前会 WaitForAnyActiveAnimUpdateToComplete() 等待异步动画更新 entity.h:942-944、 entity.cpp:659-665。

附着状态枚举(ATTACH_STATE_*,低 4 位)与外部/内部附着标志在 attachmententityextension.h 中定义,单附着最多 ATTACHMENT_MAX_CONSTRAINTS = 4 个约束 src/dev_ng/rage/framework/src/entity/attachmententityextension.h:16、 attachmententityextension.h:38-90。附着的完整状态机(物理附着、上下车、坐骑等)由游戏层驱动,本页不展开。

3.7 与物理的关联#

  • SetCurrentPhysicsInst(inst):断言此前为空(注释:替换 inst 会搞乱引用计数),设置后把 phInst::userData 指回实体并置 phfwInst::FLAG_USERDATA_ENTITY src/dev_ng/rage/framework/src/entity/entity.cpp:534-544。
  • SwitchCurrentPhysicsInst 可替换(可选继承 FLAG_NEVER_ACTIVATE) src/dev_ng/rage/framework/src/entity/entity.h:786-795。
  • 碰撞体缓存:UpdateCurrentCollider() 从 PHSIM->GetCollider(inst) 取值写入动态组件;只在拿到非空碰撞体时才创建组件 entity.cpp:513-526; 物理激活/休眠回调 OnActivate/OnDeactivate 触发刷新 entity.cpp:429-443; __DEV 下 GetCurrentCollider() 会校验缓存与模拟器一致 entity.cpp:407-426。
  • GetFragInst() 直接把当前物理实例强转为 fragInst*(不做类型检查) entity.h:780-783。
  • 析构时断言子类已清除物理实例 entity.cpp:132-133。

3.8 与 drawable 的关联:fwDrawData#

m_pDrawHandler 指向 fwDrawData:持有 rmcDrawable 的句柄(pgRef/pgHandle2,SPU 下为二级指针)、共享绘制数据偏移 dlSharedDataInfo、可选 fwCustomShaderEffect 实例,以及 15 位 m_phaseLODs、2 位 m_lastLODIdx、m_hasBones、8 位遮挡查询 ID src/dev_ng/rage/framework/src/entity/drawdata.h:125-192。 AddToDrawList(entity, params) 是生成绘制命令的虚接口(细节见 ../11-rendering/drawlists-and-render-thread-handoff.md)。 fwCustomShaderEffect 的接口按线程标注:SetShaderVariables 在渲染线程、AddToDrawList 在绘制列表构建时调用,最多 16 种类型(4 位) drawdata.h:33-73。 ActiveEntity 记录实体、分数、最后可见帧和原型的 strIndex,构造时通过 fwArchetypeManager::GetStreamingModule()->GetStreamingIndex(modelIndex) 求得 drawdata.h:83-120、 drawdata.h:209-216。析构时 delete m_pDrawHandler entity.cpp:118-121。

3.9 生命周期#

证据:ConstructEntity 校验依赖并调用 CreateEntity/SetModelId/InitEntityFromDefinition src/dev_ng/rage/framework/src/fwscene/mapdata/mapdata.cpp:69-149; InitEntityFromDefinition 的步骤 src/dev_ng/rage/framework/src/entity/entity.cpp:471-500; fwArchetype::CreateEntity 基类实现返回 NULL,由游戏层覆写 src/dev_ng/rage/framework/src/entity/archetype.cpp:181-184。批量实体(fwPropInstanceListDef/fwGrassInstanceListDef)走专用重载,基类版本直接断言 entity.cpp:502-511。若某原型所在 .ityp 不是该 .imap 的依赖、又不是“常驻 DLC”,实体会被跳过并断言 mapdata.cpp:119-127。

原型保护:不经 IMAP 创建、却引用流式原型的实体(脚本创建的物体等)应调用 ProtectStreamedArchetype():对 .ityp 槽加 REF_OTHER 引用并置 PROTECT_ARCHETYPE;析构或 UnprotectStreamedArchetype() 时调度移除—— 通过 gDrawListMgr->AddTypeFileReference() 让引用延迟到渲染线程不再使用后再释放 src/dev_ng/rage/framework/src/entity/entity.cpp:125-128、 entity.cpp:149-169、 src/dev_ng/rage/framework/src/entity/archetypemanager.cpp:553-587。

析构:通知贴花管理器 DECALMGR.OnDelete(this)、删除绘制句柄与动态组件、释放原型保护、断言已不在容器/物理/场景更新中 entity.cpp:103-147。

模型索引:GetModelIndex() 返回原型的流式槽位;注释警告不要缓存,因为流式原型的模型索引在运行期不是常量 src/dev_ng/rage/framework/src/entity/entity.h:171-173。 GetModelId() 通过名字哈希查 fwArchetypeManager 映射重建完整 fwModelId entity.h:178-179。

3.10 场景更新 fwSceneUpdate#

解决“只对需要的实体每帧调某个回调”的问题,避免遍历全部实体:

  • 最多 32 种更新类型,每种对应一个位;RAGE 只定义 SU_DYNAMIC = BIT0,游戏从 USER_SCENE_UPDATE = BIT1 起定义 src/dev_ng/rage/framework/src/entity/sceneupdate.h:38-48。
  • 参与实体的扩展对象存放在固定数组 atFixedArray<fwSceneUpdateExtension, 1560> 中(MAX_SCENE_UPDATE_NODES = 1560) sceneupdate.h:41、 sceneupdate.h:101。
  • RegisterSceneUpdate(bit, cb, name) 断言不重复注册;OverrideSceneUpdate 允许覆盖 src/dev_ng/rage/framework/src/entity/sceneupdate.cpp:109-134。 游戏在 CGameWorld 初始化时注册了多种回调(如 SU_PRESIM_PHYSICS_UPDATE、SU_UPDATE_VEHICLE) src/dev_ng/game/scene/world/GameWorld.cpp:405-409。
  • AddToSceneUpdate 第一次时从数组 Append 一个扩展并挂到实体扩展表 sceneupdate.cpp:141-158。
  • RemoveFromSceneUpdate:位清空后若正处于更新遍历中则放入延迟删除表;否则 Unlink 并 DeleteFast(用尾元素填洞), 被搬动的扩展需要从其实体的扩展链表中重新 Unlink/Add sceneupdate.cpp:160-224。
  • RunCustomCallbackBits 在更新线程上用栈上 Alloca 的延迟删除表,遍历结束后统一执行删除 sceneupdate.cpp:320-363。 __BANK 下可只更新“选中实体”,并记录每实体每类型耗时(m_timeSpent[32]) sceneupdate.cpp:243-280。

3.11 实体类型与池#

fwEntity 本身没有类级池;具体实体池(CBuilding、CObject、CDummyObject 等)由游戏在 CScene 初始化时创建,同一处还初始化了 fwDynamicArchetypeComponent、fwDynamicEntityComponent、fwKnownRefHolder 的池 src/dev_ng/game/scene/scene.cpp:420-442。实体类型 m_type 的取值、各子类与池尺寸见 ../05-world/world-and-entities.md。


4. fwArchetype#

4.1 布局(80 字节 / 64 位 112 字节)#

编译期断言 CompileTimeAssertSize(fwArchetype, 80, 112) src/dev_ng/rage/framework/src/entity/archetype.h:389。

成员说明证据
m_extensionList原型扩展(2D 特效等)archetype.h:269
m_modelNamestrStreamingObjectName(__FINAL/__NO_OUTPUT 下只有哈希)archetype.h:270、streamingmodule.h:34-42
m_BoundingSphere、m_BoundingBox包围体;LOD 距离与 HD 纹理距离复用 AABB 的两个 user float(w 分量)archetype.h:133-140、archetype.h:273-274
m_flags:31 + m_streaming:1原型标志(最高位留给“是否流式原型”)archetype.h:120-124、archetype.h:276-277
m_DynamicComponent惰性 fwDynamicArchetypeComponent*archetype.h:279
m_DrawableType + union(m_fragmentIndex/m_drawableIndex/m_drawableDictionaryIndex) + m_drawableDictionaryDrawableIndex资产绑定archetype.h:282-290
m_numRefs:15 + m_bIsModelMissing:1引用计数(流式模块用)与缺失标记archetype.h:292-293
m_assignedStreamingSlot在 fwArchetypeManager 池中的槽 = 模型索引archetype.h:111-115

原型标志的框架位只有 MODEL_HAS_LOADED、MODEL_HAS_ANIMATION、MODEL_DRAW_LAST,其余由游戏派生类使用 archetype.h:61-65。

4.2 资产绑定(DrawableType)#

DrawableType资产设置函数取 drawable
DT_DRAWABLEDICTIONARYg_DwdStore 槽 + 字典内条目索引SetDrawDictFilepDwd->GetEntry(m_drawableDictionaryDrawableIndex)
DT_DRAWABLEg_DrawableStore 槽SetDrawableOrFragmentFileg_DrawableStore.Get
DT_FRAGMENTg_FragmentStore 槽SetDrawableOrFragmentFilefragType::GetCommonDrawable()
DT_ASSETLESS无资产SetDrawableTypeAsAssetless—
DT_UNINITIALIZED未绑定/缺失—NULL

证据:枚举 archetype.h:52-59; GetDrawable 仅在 MODEL_HAS_LOADED 时返回 src/dev_ng/rage/framework/src/entity/archetype.cpp:213-245; SetDrawDictFile(名字为 "null" 时置 UNINITIALIZED;找不到 .ydd 则 Verifyf 失败) archetype.cpp:268-298; SetDrawableOrFragmentFile 先查 drawable store、再查 fragment store,都没有时仅 artWarningf archetype.cpp:304-349。

父纹理字典:原型定义中的 m_textureDictionary 解析为 TXD 槽后,写到 drawable/dwd/frag 槽的“父 TXD”(这正是流式依赖的来源,见 streaming-engine.md §3.4);同一 DWD 的条目若指定不同父 TXD 会 artAssertf(注释:通常是 SLOD 子对象用了不同 TXD) archetype.cpp:290-297、 archetype.cpp:351-360。

4.3 fwDynamicArchetypeComponent#

只有部分原型需要的数据(剪辑字典、混合形状文件、表情字典与哈希、姿态匹配文件、生物元数据、物理原型 phArchetypeDamp*、物理原型引用数、AI 规避点、自动播放动画、"物理在 drawable 中")放在 48 字节的惰性组件中 src/dev_ng/rage/framework/src/entity/dynamicarchetype.h:14-93,池为 FW_INSTANTIATE_CLASS_POOL_SPILLOVER(fwDynamicArchetypeComponent, 2900, 0.10f, ...) src/dev_ng/rage/framework/src/entity/dynamicarchetype.cpp:13。 __BANK 下池满前会转储所有持有组件的原型 archetype.cpp:437-457。 SetPhysics 在挂物理原型时用边界体扩展原型包围体(保留 AABB 的 user float) archetype.cpp:412-435、 archetype.cpp:479-508。

4.4 初始化与销毁#

InitArchetypeFromDefinition(mapTypeDefIndex, def, bHasAssetReferences) src/dev_ng/rage/framework/src/entity/archetype.cpp:85-178:

  1. Init() 清零(默认 LOD 距离 100、m_drawableDictionaryDrawableIndex=0xFF 作为非法哨兵) archetype.cpp:60-81;
  2. 按 .ityp 是否常驻(fwMapTypesDef::GetIsPermanent())调用 RegisterPermanentArchetype 或 RegisterStreamedArchetype archetype.cpp:101-108;
  3. 拷贝 LOD 距离、包围体、HD 纹理距离、剪辑字典;有 m_physicsDictionary 则标记“物理在 drawable 中”;
  4. 绑定资产或置 DT_ASSETLESS;__ASSERT 下与定义里的 m_assetType 比对(对一个 gen8 占位内容的特例豁免) archetype.cpp:141-177。

谁调用它:fwMapTypes::ConstructArchetypes 为 .ityp 分配一个工厂存储仓,按定义的 parser 结构名查工厂 ID,再 GetArchetypeFactory(factoryId)->CreateBaseItem(mapTypeDefIndex) 构造原型并初始化 src/dev_ng/rage/framework/src/fwscene/mapdata/maptypes.cpp:93-135。

Shutdown() 释放物理原型引用并销毁扩展;析构时若原型持有流式槽:销毁动态组件、断言流式引用为 0、UnregisterStreamedArchetype,再释放非 fragment 的物理原型 archetype.cpp:33-57、 archetype.cpp:197-208。


5. fwArchetypeManager 与 fwModelId#

5.1 存储#

结构类型说明证据
ms_ArchetypePoolatIteratablePool<fwArchetype*>槽号 = 模型索引;存指针而非对象archetypemanager.h:148、archetypemanager.h:368-374
ms_ArchetypeMapatSimplePooledMapType<u32, fwModelId>模型名哈希 → fwModelIdarchetypemanager.h:149
ms_MaxArchetypesu16默认 32200,注释说明会被 gameconfig 覆盖archetypemanager.cpp:59
ms_archetypeFactoryMgr / ms_2dEffectFactoryMgrfwFactoryManager<>原型/2D 特效工厂表archetypemanager.h:284-285

游戏在初始化时用池名 "fwArchetypePooledMap" 的 gameconfig 尺寸调用 SetMaxArchetypes src/dev_ng/game/system/system.cpp:961;一旦已被使用再修改会断言,且必须 < MI_INVALID(0xffff) archetypemanager.cpp:215-223。 Init(maxFactoryId) 初始化工厂表、池与映射 archetypemanager.cpp:157-169;游戏侧以 MI_HIGHEST_FACTORY_ID 调用并注册 Base/Time/Weapon/Vehicle/Ped/MLO/Composite 工厂 src/dev_ng/game/modelinfo/ModelInfo.cpp:535-560。

5.2 fwModelId 位布局(4 字节)#

位段宽度含义
m_modelIndex16原型池槽号;MI_INVALID = 0xffff
m_mapTypeDefIndex12定义它的 .ityp 槽;TF_INVALID = 0xfff
m_bIsStreamed1是否流式原型(IsGlobal() == !streamed)
m_bIsMemLocked1MemLock 标记
m_bPad1/22填充

证据:src/dev_ng/rage/framework/src/entity/archetypemanager.h:65-140。注释强调“流式索引 ≠ 模型索引”,转换时只保留模型索引部分 archetypemanager.h:104-106。 MemLock 是一个对模型索引做可逆变换的机制,但受 __ENABLE_MEMLOCK 控制且该宏为 0,即当前构建不生效 src/dev_ng/rage/framework/src/entity/archetypemanager.cpp:84-120(本页不描述其细节)。

5.3 常驻 vs 流式原型#

RegisterPermanentArchetypeRegisterStreamedArchetype
前提管理器未锁定(LockPermanentArchetypes() 后再注册会断言并返回 MI_INVALID)无锁定检查
池满PoolFullCallback() 转储后 Quitf("Ran out of permanent archetypes")同上 Quitf("Ran out of streamed archetypes")
原型标记SetPermanentStreamSlot(m_streaming=false)SetTempStreamSlot(m_streaming=true)
fwModelId.m_bIsStreamedfalsetrue
注销—UnregisterStreamedArchetype:清池槽、删映射,并 UnregisterObject 其流式 dummy

证据:archetypemanager.cpp:313-354、 archetypemanager.cpp:410-466、 archetypemanager.h:217。 .ityp 是否常驻取决于 fwMapTypesDef;命令行 -PartialDynArch 会把所有 .ityp 标为常驻 src/dev_ng/rage/framework/src/fwscene/stores/maptypesstore.cpp:57-60。 IsArchetypePermanentDLC 识别“流式但在当前内容上下文(如 MP)常驻”的原型 archetypemanager.cpp:468-486。

NUM_STREAMED_SLOTS = 2000(仅内景)与 NUM_STREAMED_SLOTS_FULL = 35000(完整 .imap 依赖流式)两个常量通过 GetNumStreamedSlots() 选择,可被 -numStreamedSlots 覆盖(上限 0xfffe),-PartialDynArch 时取 2000 archetypemanager.h:170-174、 archetypemanager.cpp:140-155。

5.4 工厂与按文件分仓#

fwFactoryWithDynamicStore<T,Base> 以 atMap<s32 storageBlockId, atArray<T>*> 为每个 .ityp 槽分配一个连续数组(“silo”), CreateItem 只是 Append(容量不足会断言);FreeStorageBlock 整块 delete src/dev_ng/rage/framework/src/entity/factory.h:108-222。 fwFactoryWithPoolStore 改用外部 fwPool<T>,满时返回 NULL factory.h:61-100。特殊块 ID:fwFactory::GLOBAL = 0xffff、EXTRA = 0xf000 factory.h:11-16。

FreeArchetypes(mapTypeDefIndex) 分三遍:对每个原型工厂 ForAllUsed(..., Shutdown);释放原型存储块(PS3/360 上切到 frag cache 内存);释放 2D 特效扩展存储块 src/dev_ng/rage/framework/src/entity/archetypemanager.cpp:497-536。它在非常驻 .ityp 被移出时调用 src/dev_ng/rage/framework/src/fwscene/stores/maptypesstore.cpp:72-83。

边界:工厂“按文件分仓 + 整块释放”意味着原型对象的地址在 .ityp 驻留期间稳定,但 .ityp 一旦释放所有原型同时失效—— 这就是需要 PROTECT_ARCHETYPE 与渲染线程延迟引用(§3.9)的原因。


6. fwArchetypeStreamingModule:原型作为“哑”流式对象#

原型自己没有文件;它在流式系统中以 dummy streamable 存在:请求一个模型 = 请求该 dummy,流式系统据其依赖拉取 drawable/frag/dwd(以及它们的父 TXD),依赖全部就绪后“完成” dummy 并调用模块 Load。

  • 模块名 "ModelInfo"、文件类型 -1(无扩展名映射)、大小等于原型上限、每页 1024 src/dev_ng/rage/framework/src/entity/archetypestreamingmodule.cpp:20-23; 游戏派生 CModelInfoStreamingModule 并注册,模块 ID 写回 fwArchetypeManager::SetStreamingModuleId src/dev_ng/game/modelinfo/ModelInfo.cpp:164-167、 ModelInfo.cpp:1661。
  • Register(name) 直接断言——原型不经 RPF 注册,而由游戏以 RegisterDummyObject 登记 archetypestreamingmodule.cpp:32-36、 src/dev_ng/game/modelinfo/ModelInfo.cpp:1045; RegisterDummyObject 给 strStreamingInfo 置 STRFLAG_INTERNAL_DUMMY,若同名已有真实文件则报错 src/dev_ng/rage/framework/src/streaming/streaminginfo.cpp:4497-4545。
  • 引用计数直接用 fwArchetype::m_numRefs(15 位),不区分 strRefKind archetypestreamingmodule.cpp:38-53。
  • GetDependencies 按 DrawableType 返回一个依赖(DWD/frag/drawable 的 strIndex),ASSETLESS 无依赖 archetypestreamingmodule.cpp:54-97。
  • Load 对 DWD 原型在字典中按模型名哈希查条目;查不到则 FlagModelAsMissing() 并返回 false(注释:防止流式卡住), 对 frag/drawable 只做断言 archetypestreamingmodule.cpp:129-183。
  • Remove 不应被调用(断言)——dummy 的卸载由游戏派生类处理 archetypestreamingmodule.cpp:99-126。

完整的请求时序见 streaming-engine.md §8。


7. fwDecorator:脚本/游戏的键值装饰器#

7.1 问题与数据模型#

装饰器让脚本(以及网络同步、部分游戏代码)在任意 fwExtensibleBase 上挂“名字 → 值”的小数据,而不需要为每种需求改实体类。

  • fwDecorator:键 atHashWithStringBank m_Key、类型 TYPE_FLOAT/BOOL/INT/STRING/TIME、一个 32 位 union 值、m_Next 链指针; 字符串类型只存哈希 src/dev_ng/rage/framework/src/fwdecorator/decorator.h:16-79、 src/dev_ng/rage/framework/src/fwdecorator/decorator.cpp:174-180。
  • fwDecoratorExtension : fwExtension:单链表头 m_Root + 计数;__BANK(DECORATOR_DEBUG == __BANK)下额外记下宿主指针用于调试绘制 src/dev_ng/rage/framework/src/fwdecorator/decoratorExtension.h:26-118、 src/dev_ng/rage/framework/src/fwdecorator/decorator_channel.h:35。
  • 两个池:fwDecorator 为 FW_INSTANTIATE_CLASS_POOL_SPILLOVER(fwDecorator, 600, 0.56f, "Decorator") decorator.cpp:41; fwDecoratorExtension 尺寸完全来自配置(CONFIGURED_FROM_FILE,COMMERCE_CONTAINER 时不用 flex 堆) src/dev_ng/rage/framework/src/fwdecorator/decoratorExtension.cpp:101-105。 两个池只在 INIT_SESSION 创建、SHUTDOWN_SESSION 销毁 src/dev_ng/rage/framework/src/fwdecorator/decoratorInterface.cpp:40-69。

7.2 名字/类型注册表 fwDecoratorInterface#

全局单例 gp_DecoratorInterface 由游戏注入派生类 CDecoratorInterface decoratorInterface.cpp:71-85、 src/dev_ng/game/Core/game.cpp:1126,其 Init/Shutdown 挂在 gameSkeleton 上 game.cpp:2420、 game.cpp:2597。

  • Register(key, type, who):首次插入 m_Registered[hash] = type;重复注册且类型不同时,在 DECORATOR_DEBUG 构建中 Quitf(报告双方注册者),非调试构建静默保留首次类型 decoratorInterface.cpp:126-151。
  • 每次 Get/Set 都先 RegisteredCheck(未注册则断言并拒绝),再 TypeCheck decorator.cpp:84-212。

7.3 操作映射#

操作实现效果
fwDecoratorExtension::Set(base, key, val)无扩展则 rage_new 并 Add 到扩展表;Update 找不到键就新建对应类型的 fwDecorator 并头插decoratorExtension.h:41-52、decoratorExtension.cpp:107-174
Get(base, key, out)线性查找链表见下方边界
ExistsOn线性查找decoratorExtension.cpp:30-39
RemoveFrom / RemoveAllFrom删除节点;计数为 0 时 DestroyExtension<fwDecoratorExtension>()decoratorExtension.cpp:41-72

边界:模板 Get(key, out) 找到键后调用 dec->Get(out) 但忽略其返回值,始终返回 valid = true decoratorExtension.h:83-93。因此以错误类型读取已存在的装饰器时,调用方会得到 “成功” 但 out 未被写入(仅 decAssertf 报类型不匹配)。

7.4 上层接口#

  • 脚本命令 DECOR_SET_*/DECOR_GET_*/DECOR_EXIST_ON/DECOR_REMOVE/DECOR_REGISTER/DECOR_REGISTER_LOCK 等通过脚本 GUID 找到 fwExtensibleBase src/dev_ng/game/script/commands_decorator.cpp:28-60、 commands_decorator.cpp:249-273。 “注册锁”只存在于脚本命令层:DECOR_REGISTER_LOCK 之后再注册会断言并忽略,SHUTDOWN_SESSION 时解锁 commands_decorator.cpp:18-26、 commands_decorator.cpp:218-241。
  • 网络对象同步会整体替换远端实体的装饰器(先 RemoveAllFrom 再逐个 Set) src/dev_ng/game/network/Objects/Entities/NetObjPlayer.cpp:3037-3066, 细节见 ../17-network-sync/sync-trees-and-serialisation.md。
  • __BANK 下有 “Decorators” 调试 bank(渲染所有装饰器、导出列表),位置通过 dynamic_cast<fwEntity*> 取得 decoratorInterface.cpp:294-339。

8. 约束与边界行为汇总#

约束说明证据
线程扩展表增删、场景更新登记必须在更新线程extensionlist.cpp:21、sceneupdate.cpp:143
固定尺寸fwEntity 192 字节、fwArchetype 80/112 字节、fwDynamicArchetypeComponent 48 字节,靠编译期断言守护entity.h:584-596、archetype.h:389、dynamicarchetype.h:93
32 位非 SPU 构建fwDynamicEntityComponent 必须小于 smallocator 上限(注释称超出时应改用池;当前已用池)dynamicentitycomponent.h:177-182
位 15 共用HAS_NON_WATER_REFLECTION_PROXY_CHILD 与 HAS_HYBRID_ALPHA 同值entity.h:120-121
模型索引可变流式原型的模型索引不可缓存entity.h:172
原型容量原型池满直接 Quitfarchetypemanager.cpp:321-329
扩展池满地图实例扩展创建失败只断言entity.cpp:464-467
装饰器类型读取类型不符时 Get 仍返回 truedecoratorExtension.h:83-93

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

  • fwAltSkeletonExtension、fwCameraRelativeExtension 的使用者与语义未追踪(仅确认其为 fwExtension 子类)。
  • ENABLE_MATRIX_MEMBER=1 下 fwSimpleTransform/fwQuaternionTransform 池的剩余调用者未穷举。
  • 游戏层 CModelInfoStreamingModule 覆写了 GetDependencies/Load(ModelInfo.cpp:227、438),其附加依赖(如剪辑字典、物理字典)归 ../05-world/model-info.md,本页未展开。