实体与原型:fwEntity / fwArchetype / 扩展机制 / fwDecorator
本页解释 RAGE Framework 层(rage/framework/src/entity 与 fwdecorator)如何表示“世界中的一个东西”: fwEntity 是所有世界实体(建筑、车、人、物体、灯光……)的公共基类,fwArchetype 是被许多实体共享的“模型定义”,二者通过 fwModelId/流式槽位相连;扩展(fwExtension)和装饰器(fwDecorator)让上层在不修改基类的前提下往实体上挂数据。理解这一层是读懂 CEntity 类层次、地图加载、流式依赖与脚本数据挂载的前提。
背景与相关页面#
- 游戏层的
CEntity/CBuilding/CDynamicEntity/CPhysical等派生类与各自的池:见 ../05-world/world-and-entities.md。 CBaseModelInfo等fwArchetype的游戏层派生、ModelIndices:见 ../05-world/model-info.md。- IMAP/ITYP 内容、LOD 层次(
fwLodData的语义与 alpha 淡入淡出):见 ../05-world/map-data-and-lod.md。 - 实体在世界表示中的登记(
fwEntityDesc、容器、可见性):见 scene-graph-and-visibility.md。 - 原型作为“哑(dummy)流式对象”如何拉起 drawable/纹理依赖:见 streaming-engine.md。
fwPool、fwRegdRef/fwRefAwareBase、fwConfigManager池尺寸:见 framework-systems.md。- 物理实例与碰撞体:见 ../09-physics/rage-physics-engine.md;动画导演
fwAnimDirector:见 ../10-animation/game-animation-and-ik.md。 - 脚本 GUID → 实体(装饰器命令用到):见 ../14-script-runtime/script-handlers-and-threads.md;装饰器网络同步:见 ../17-network-sync/sync-trees-and-serialisation.md。
相关源码文件#
| 文件 | 内容 |
|---|---|
rage/framework/src/entity/entity.h/.cpp | fwEntity 定义、标志位、变换、物理/骨骼/LOD 访问器、从地图定义初始化 |
rage/framework/src/entity/extensiblebase.h、extensionlist.h/.cpp | fwExtensibleBase、fwExtension、fwExtensionList、扩展工厂 typedef |
rage/framework/src/entity/transform.h/.inl/.cpp | fwTransform 族(矩阵/四元数/简化/恒等变换)与其分页池 |
rage/framework/src/entity/dynamicentitycomponent.h/.cpp | fwDynamicEntityComponent:动态实体的惰性附加组件 |
rage/framework/src/entity/attachmententityextension.h/.cpp | fwAttachmentEntityExtension:附着状态机与约束 |
rage/framework/src/entity/drawdata.h/.cpp | fwDrawData、fwCustomShaderEffect、ActiveEntity |
rage/framework/src/entity/sceneupdate.h/.cpp | fwSceneUpdate:按位注册的逐实体更新回调 |
rage/framework/src/entity/archetype.h/.cpp、dynamicarchetype.h/.cpp | fwArchetype、fwDynamicArchetypeComponent |
rage/framework/src/entity/archetypemanager.h/.cpp、factory.h | fwArchetypeManager、fwModelId、工厂与按 .ityp 分仓存储 |
rage/framework/src/entity/archetypestreamingmodule.h/.cpp | fwArchetypeStreamingModule("ModelInfo" 流式模块) |
rage/framework/src/entity/altskeletonextension.*、camerarelativeextension.* | 两个小型实体扩展 |
rage/framework/src/fwdecorator/* | fwDecorator、fwDecoratorExtension、fwDecoratorInterface |
rage/framework/src/fwscene/lod/LodTypes.h | fwLodData(嵌在 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_pArchetype | fwArchetype* | 所属原型 | entity.h:542 |
m_type | u8 | 实体类型(值由游戏层 ENTITY_TYPE_* 定义),SetType 会触发 UpdateRenderFlags() | entity.h:165-167 |
m_protectedFlags | u8 | 受保护标志(§3.2) | entity.h:544 |
m_RenderFlags | u16 | 渲染标志,低 10 位留给 RAGE,游戏位需左移 fwRenderFlag_USER_FLAG_SHIFT | entity.h:157-162 |
m_baseFlags | u32 | 基础标志(§3.2) | entity.h:549 |
m_CurrentPhysicsInst | phInst* | 当前物理实例 | entity.h:550 |
m_phaseVisibilityMask | fwFlags16 | 渲染阶段可见性掩码(哪些 phase 子集可渲染此实体),构造时全置位 | entity.h:410-415、entity.cpp:81 |
m_visibilityType | u16 | 游戏定义的“种类”,渲染阶段可据此剔除 | entity.h:417-420 |
m_container | union | 所属实体容器指针 或 “保留列表 ID”(§3.5) | entity.h:556-560 |
m_pDrawHandler | fwDrawData* | 绘制句柄(drawable + 着色器实例) | entity.h:562 |
m_DynamicEntityComponent | fwDynamicEntityComponent* | 惰性动态组件(§3.6) | entity.h:565 |
m_Transform | fwMatrixScaledTransform | 变换(§3.3) | entity.h:566-570 |
m_lodData | fwLodData | LOD 层次与 alpha(§3.4) | entity.h:572 |
m_viewportInstancedRenderBit | u8 | 仅 RAGE_INSTANCED_TECH | entity.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:
| 位 | 名称 | 含义(据注释) |
|---|---|---|
| 0 | IS_VISIBLE | 参与可见性(构造时默认置位) |
| 1 | IS_SEARCHABLE | 参与世界搜索(构造时默认置位) |
| 2–7 | HAS_OPAQUE/ALPHA/DECAL/CUTOUT/WATER/DISPL_ALPHA | 参与的绘制 pass |
| 8 / 14 | HAS_PRERENDER / OK_TO_PRERENDER | 有预渲染效果 / 允许预渲染 |
| 9 | IS_LIGHT | 灯光实体(进入外景灯光容器) |
| 10–13 | USE_SCREENDOOR、SUPPRESS_ALPHA、FORCE_ALPHA、RENDER_SMALL_SHADOW | 透明/阴影控制 |
| 15 | HAS_NON_WATER_REFLECTION_PROXY_CHILD 与 HAS_HYBRID_ALPHA | 同一位被两个名字共享(注释 SHARED(15)) |
| 16 | IS_DYNAMIC | 动态实体(进入外景动态容器) |
| 17 / 18 | IS_FIXED / IS_FIXED_BY_NETWORK | 物理固定 / 网络克隆固定 |
| 19 | SPAWN_PHYS_ACTIVE | 加入世界时激活物理 |
| 20 | IS_TIMED_ENTITY | 使用时间段模型 |
| 21 / 22 | DONT_STREAM / LOW_PRIORITY_STREAM | 流式提示 |
| 23 | HAS_PHYSICS_DICT | — |
| 24 | REMOVE_FROM_WORLD | 下次处理时移除 |
| 25 | FORCED_ZERO_ALPHA | 被(脚本)强制 alpha=0 |
| 26 / 27 | DRAW_LAST / DRAW_FIRST_SORTED | 排序提示 |
| 28 | NO_INSTANCED_PHYS | 不实例化碰撞 |
| 29 | HAS_HD_TEX_DIST | 支持 HD 顶层 mip 流式 |
| 30 | PROTECT_ARCHETYPE | 实体对流式原型持有 .ityp 引用(§3.9) |
| 31 | HAS_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_SCALED | 0 / 2 | 位置 + 航向(仅绕 Z) |
TYPE_MATRIX / TYPE_MATRIX_SCALED | 1 / 3 | 完整 3x4 矩阵(缩放存于列的 w 分量) |
TYPE_IDENTITY | 4 | 单例恒等变换 |
TYPE_QUATERNION / TYPE_QUATERNION_SCALED | 8 / 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 列 wsrc/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:- 若原型有剪辑字典且
GetHasAnimation(),视为dynamic; scaled = (scaleXY≠1 || scaleZ≠1) && 原型不是 DT_FRAGMENT,并断言缩放在 (0,4) 内entity.cpp:185-189;- 若
dynamic、或四元数 |x|/|y| > 0.05、或带FLAG_FULLMATRIX且 x/y 非零 → 用完整四元数构造矩阵entity.cpp:197-212; - 否则视为只有航向:由
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_ENTITYsrc/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/Addsceneupdate.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_modelName | strStreamingObjectName(__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_DRAWABLEDICTIONARY | g_DwdStore 槽 + 字典内条目索引 | SetDrawDictFile | pDwd->GetEntry(m_drawableDictionaryDrawableIndex) |
DT_DRAWABLE | g_DrawableStore 槽 | SetDrawableOrFragmentFile | g_DrawableStore.Get |
DT_FRAGMENT | g_FragmentStore 槽 | SetDrawableOrFragmentFile | fragType::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:
Init()清零(默认 LOD 距离 100、m_drawableDictionaryDrawableIndex=0xFF作为非法哨兵)archetype.cpp:60-81;- 按
.ityp是否常驻(fwMapTypesDef::GetIsPermanent())调用RegisterPermanentArchetype或RegisterStreamedArchetypearchetype.cpp:101-108; - 拷贝 LOD 距离、包围体、HD 纹理距离、剪辑字典;有
m_physicsDictionary则标记“物理在 drawable 中”; - 绑定资产或置
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_ArchetypePool | atIteratablePool<fwArchetype*> | 槽号 = 模型索引;存指针而非对象 | archetypemanager.h:148、archetypemanager.h:368-374 |
ms_ArchetypeMap | atSimplePooledMapType<u32, fwModelId> | 模型名哈希 → fwModelId | archetypemanager.h:149 |
ms_MaxArchetypes | u16 | 默认 32200,注释说明会被 gameconfig 覆盖 | archetypemanager.cpp:59 |
ms_archetypeFactoryMgr / ms_2dEffectFactoryMgr | fwFactoryManager<> | 原型/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_modelIndex | 16 | 原型池槽号;MI_INVALID = 0xffff |
m_mapTypeDefIndex | 12 | 定义它的 .ityp 槽;TF_INVALID = 0xfff |
m_bIsStreamed | 1 | 是否流式原型(IsGlobal() == !streamed) |
m_bIsMemLocked | 1 | MemLock 标记 |
m_bPad1/2 | 2 | 填充 |
证据: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 流式原型#
RegisterPermanentArchetype | RegisterStreamedArchetype | |
|---|---|---|
| 前提 | 管理器未锁定(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_bIsStreamed | false | true |
| 注销 | — | 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(无扩展名映射)、大小等于原型上限、每页 1024src/dev_ng/rage/framework/src/entity/archetypestreamingmodule.cpp:20-23; 游戏派生CModelInfoStreamingModule并注册,模块 ID 写回fwArchetypeManager::SetStreamingModuleIdsrc/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 位),不区分strRefKindarchetypestreamingmodule.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(未注册则断言并拒绝),再TypeCheckdecorator.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 = truedecoratorExtension.h:83-93。因此以错误类型读取已存在的装饰器时,调用方会得到 “成功” 但out未被写入(仅decAssertf报类型不匹配)。
7.4 上层接口#
- 脚本命令
DECOR_SET_*/DECOR_GET_*/DECOR_EXIST_ON/DECOR_REMOVE/DECOR_REGISTER/DECOR_REGISTER_LOCK等通过脚本 GUID 找到fwExtensibleBasesrc/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 |
| 原型容量 | 原型池满直接 Quitf | archetypemanager.cpp:321-329 |
| 扩展池满 | 地图实例扩展创建失败只断言 | entity.cpp:464-467 |
| 装饰器类型 | 读取类型不符时 Get 仍返回 true | decoratorExtension.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,本页未展开。