系统基础、容器、数学与内存体系
范围:Base system/atl/math/mathext/vector/vectormath/data/string,以及 paging 引用。静态分析,未运行。入口:Summary。
1. 内存是架构的一部分#
RAGE 不将所有数据交给同一个默认堆。sysMemAllocator 统一分配/释放接口,具体实现包含 fixed/pool、buddy、best-fit、virtual、sparse、external、multi/split 等;资源和游戏对象又按用途进入不同 heap/memory bucket。
这让峰值、对齐、可移动性和平台 GPU 访问要求能够被显式控制。代价是“这个指针由谁分配、谁释放、能否移动、什么时候可以释放”成为每个对象的必要知识。
2. 分配器接口的细节#
接口包含 size、alignment、heap index,提供 Allocate 与可失败的 TryAllocate;SetQuitOnFail 能改变失败行为。Allocate 注释中“永不 NULL”与可配置失败策略应一起理解,不能只引用一句旧说明就断言调用者不需要考虑失败。
Resize 基类默认断言不支持,且参数语义是缩小;它不是所有分配器都支持的通用 realloc。AllocateMap/FreeMap 又让支持资源多块映射的 allocator 直接处理资源图。
current/master allocator、scope、memory bucket 与 user data 让临时切换分配目标及统计归属成为可能;移植时必须核对相关变量在目标宏下的 TLS/全局定义,而非凭 GetCurrent 名称假设线程局部。
3. 按生命周期选分配器#
| 数据类型 | 合理机制及源码对应 | 性能特点 | 风险 |
|---|---|---|---|
| 大量同尺寸实例 | atl/pool、pool/fixed allocator | 分配快、对象定位便宜、容量可预测 | 池耗尽、槽位重用、预留过大 |
| 单阶段临时数据 | task scratch、局部 arena 风格分配 | 批量回收、少锁 | 数据逃逸到下一阶段、最坏峰值越界 |
| 可装卸资源页 | buddy allocator | 分裂/合并、支持重定位和页粒度管理 | 内部碎片、pin 阻挡合并 |
| 复杂长寿命对象 | general/simple/best-fit 等堆实现 | 更灵活 | 外部碎片、分配器争用和追踪难度 |
| 平台虚拟/稀疏资源 | virtual/sparse allocator 分支 | 结合平台映射能力 | 平台限制、提交与驻留语义不同 |
这张表表达用途选择,不声称每个模块都无条件使用对应实现;具体绑定要从 memmanager、游戏配置与对象分配入口确认。
4. 资源堆与游戏堆分离#
eMemoryType 明确包含 GAME/RESOURCE × VIRTUAL/PHYSICAL,以及 debug 分支。memory user data 又能标识 streaming index、draw pages、Scaleform、cloth、networking 等用途。
资源堆适合成块创建、按依赖卸载和搬移;游戏堆对象更常有长期指针和复杂析构。两者分离减少生命周期混杂,也允许针对内容资源做特殊恢复策略。
但只统计逻辑资源字节会低估真实内存:页对齐、allocator 元信息、空闲孔洞、指针跟踪、解压输入、CPU/GPU 双份以及搬移中的新旧拷贝都要计入。详见Streaming。
5. Buddy 与碎片整理#
sysMemBuddyAllocator 暴露 LargestAvailableBlock、Fragmentation、Defragment、LockBlock 等接口,说明 allocator 与资源系统共同维护可移动性。SparseBuddy 还有 deferred free,反映释放操作也可能需要延后到合法时机。
“总空闲足够”与“能满足指定大小和对齐”是两件事。多种大小且被长期 pin 的资源会留下无法利用的孔洞;临时大峰值也可能在最终驻留预算未满前触发失败。应同时记录总空闲、最大块、页浪费和不可移动占用。
新项目若选择虚拟地址稳定、物理页可重映射的方案,也仍须处理 GPU 同步、页表/映射开销和平台能力,不能以新 API 名称假定碎片问题消失。
6. pgRef 不是 shared_ptr#
pgRef 构造/赋值通过 known-reference 宏登记指针位置,资源装入时做 PointerFixup。析构只移除 known reference;显式 Release() 才调用 pointee 的 Release。复制构造也不是标准 shared_ptr 的自动 AddRef 语义。
这正是需要区分的两个问题:
- 引用位置追踪:对象移动时有哪些地址槽要改?
- 所有权/引用计数:谁让对象继续存活,何时销毁?
混淆它们会导致漏释放或过早释放。pgOwner、资源 constructor、streaming module 的 AddRef/RemoveRef 需要一起读,不能对所有 Ref 后缀类套用同一种 RAII 解释。
7. 容器与对象身份#
ATL 提供 array、fixed/range array、hash/map、pool、bitset 等,热点代码常用固定容量或预分配结构。atPool 让槽位索引化,iteratable pool 又支持遍历;是否具有代际校验取决于具体 pool/handle 类型,不能泛称所有池指针都能检测过期引用。
实体和资源 ID 是不同域;物理 level handle、streaming index、脚本对象引用的生命周期也不同。建议新项目强类型化 ID 并明确代际规则,在 release telemetry 中记录无效句柄来源,避免仅依靠调试断言。
紧凑 bitset 在物理 phase、可见性和状态掩码里尤为重要:它把随机对象判断变成按 word 的批量操作。代价是索引范围及稀疏高水位可能决定表大小;已删除大量对象但最大 index 仍高时,工作表未必同步缩小。
8. 数学、SIMD 与布局#
Vec3V/Vec4V/ScalarV/Mat34V 等向量类型贯穿物理和场景,传统 Vector/Matrix 与向量化 API 共存。*_In/_Out 参数约定、对齐宏、平台 intrinsic 和转置布局减少复制并让批处理适应硬件。
收益来自连续加载、并行算术和预取,而不只是“用了 SIMD”。实体 descriptor 的四对象块、BVH 量化节点、求解器缓存 collider 都是同一原则的具体化。分支多、对象随机、临时结果散落时,即使单个 dot product 向量化也难以改变整体瓶颈。
移植注意 alignment、padding、浮点舍入、NaN/denormal、endian、向量 lane 的隐含用途。代码里出现 w 存 flags 或额外数据的结构时,不应按普通三维向量操作随意覆盖它。
9. 平台内存一致性#
system 中的 interlocked、membarrier、cache 和 DMA 层服务不同硬件。某些平台内存说明明确区分 CPU cache 模式和 GPU 一致性路径;不能把“CPU 可以写、GPU 可以读”理解为完全自动一致。
任务完成、GPU fence、资源搬移结束与 allocator free 之间应有可追踪的 happens-before 关系。这里的缓存/屏障规则是算法正确性的一部分;把旧平台 DMA 调用删除但不重建所有权协议,可能留下难以复现的并发错误。
10. 面向新项目的建议与验证#
先定义四类预算:长期对象、可驻留资源、在途装载峰值和每帧临时内存。每类都要有高水位、失败行为和负责人;不要用一个“总内存还剩多少”数字管理全部系统。
对长跑和场景反复切换测量碎片与回收,不只看冷启动;对高密度角色、破碎和传送测瞬时峰值;对多 worker 测 allocator 锁争用和 false sharing。比较开发/发行配置时保留统计口径,debug heap 与标签开销可能改变行为。本次未运行这些实验。
11. 源码证据#
| 文件 | 证据 |
|---|---|
| memory.h | memory types/buckets;404 起 allocator;590–624 resource-map/current/master |
| memmanager.cpp | 内存管理装配与平台分支入口 |
| buddyallocator.h | 可用块、碎片、block lock、defrag、deferred free |
| fixedallocator.h | fixed-size 分配及 free-list |
| pool.h | pool base、索引、iteratable pool |
| ref.h | 17–65 引用追踪与显式 Release |
| resource.h | 多块资源映射和指针重定位 |
| EntityContainer.h | SoA 与尺寸断言的实际例子 |