CD Code Digest Hub /GTA V · v2

system 与诊断:线程/同步、计时、平台抽象、diag、PARAM、profile、bank

66 分钟阅读 13 个章节 2 张图

本页解释 RAGE 最底层的“运行时基座”:rage/base/src/system 中的线程与同步原语、计时器、平台标识与启动期命令行处理;diag 的通道化日志与断言;PARAM 命令行参数系统;profile 的 pf 统计、timebar、Rocky 与 Telemetry 钩子;以及 bank 调试控件与 RAG 远程 UI 的通信协议。这些模块几乎被引擎与游戏的每个 .cpp 依赖,理解它们的编译开关(__BANK、__ASSERT、__NO_OUTPUT、__STATS 等)是读懂其它子系统代码的前提。

背景与相关页面#

  • 进程入口、CommonMain、CApp 状态机:见 01-runtime/startup-and-app-fsm.md。本页只讲入口中与 sysParam / 响应文件 / 线程类型相关的片段。
  • 任务系统、sysDependency、渲染线程同步:见 01-runtime/threading-and-render-thread.md。
  • 堆布局、sysMemAllocator 的具体实现拓扑(buddy/simple/sparse 分配器等)与游戏侧池:见 01-runtime/memory-and-pools.md。本页只列接口。
  • forceinclude 配置矩阵(__DEV/__BANK/__ASSERT/__FINAL/__MASTER 各配置取值、Assert/Quitf 宏定义所在文件):见 00-overview/build-and-config.md。
  • 游戏侧调试工具(game/debug、bugstar 等)与 RAG 工具链:见 23-debug-security/debug-tools.md、24-toolchain/rage-tools-and-rag.md。
  • 文件设备(fiDeviceRemote / rfs、fiDeviceTcpIp)在 file-system-and-rpf.md。

相关源码文件#

区域关键文件
线程/IPCsystem/ipc.h、system/ipc.cpp、system/criticalsection.h/.cpp、system/spinlock.h、system/interlocked.h/.inl、system/messagequeue.h、system/tls.h、system/threadtype.h/.cpp、system/threadregistry.h、system/hangdetect.h
计时system/timer.h/.cpp、system/timemgr.h
平台与启动system/platform.h/.cpp、system/main.h、system/main.cpp、system/defaultresponsefile.cpp、system/param.h/.cpp
异常与堆栈system/exception.h/.cpp、system/stack.h/.cpp
诊断diag/channel.h/.cpp、diag/output.h、diag/tracker.h、diag/stats.h、forceinclude/win64_beta.h(断言宏样例)
profileprofile/element.h、profile/group.h、profile/page.h、profile/timebars.h、profile/settings.h、profile/rocky.h、profile/telemetry.h、profile/profiler_server.cpp
bankbank/widget.h、bank/bank.h、bank/group.h、bank/bkmgr.h/.cpp、bank/packet.h/.cpp、system/pipepacket.h

所有路径均相对 src/dev_ng/rage/base/src/。


1. 线程与 IPC 原语(sysIpc*)#

1.1 设计概要#

system/ipc.h 以不透明句柄类型(typedef struct sysIpcSemaTag *sysIpcSema 等)为每个平台提供统一的 C 风格 API,实现全部在 ipc.cpp 中按 __WIN32 / __PPU / RSG_ORBIS / __PSP2 / RSG_DURANGO 分支 src/dev_ng/rage/base/src/system/ipc.h:30-128。

概念类型 / API语义要点平台实现
信号量sysIpcSema;sysIpcCreateSema/PollSema/WaitSema/WaitSemaTimed/SignalSema/DeleteSemaWait/Signal 可带 count;sysIpcInfinite = 0xFFFFFFFFWin32 CreateSemaphore(…, 32767);Durango CreateSemaphoreExW;PS3/POSIX 用静态池 atStaticPool<sem_t,1024>;Orbis 可选 SCE 信号量 src/dev_ng/rage/base/src/system/ipc.cpp:186-222
互斥量sysIpcMutex;sysIpcCreateMutex/LockMutex/TryLockMutex/UnlockMutex允许同线程递归;只有加锁线程能解锁 src/dev_ng/rage/base/src/system/ipc.h:106-128Win32 CreateMutex;PS3 sys_lwmutex(静态池 128 个);Orbis scePthreadMutex 递归属性 src/dev_ng/rage/base/src/system/ipc.cpp:484-516
事件sysIpcEvent;sysIpcCreateEvent/WaitEvent/PollEvent/SetEvent只提供“自动复位、初始未触发”的语义(注释说明索尼平台的手动复位是等待调用的属性,无法可移植暴露)Win32 CreateEvent(NULL,FALSE,FALSE);PS3/Orbis 事件标志 + WAIT_CLEAR src/dev_ng/rage/base/src/system/ipc.cpp:1573-1650
线程sysIpcCreateThread、sysIpcCreateThreadWithCoreMask、sysIpcWaitThreadExit必须对每个有效句柄调用 WaitThreadExit 否则泄漏;栈大小下限 sysIpcMinThreadStackSize(Orbis 32 KB,其余 16 KB)src/dev_ng/rage/base/src/system/ipc.h:146-200
线程优先级enum sysIpcPriority:PRIO_IDLE=-15 … PRIO_TIME_CRITICAL=15、PRIO_MP4=60注释称“lower is more important”,但枚举数值上 HIGHEST=2;两处描述并不一致,应以各平台映射代码为准src/dev_ng/rage/base/src/system/ipc.h:146-169

sysIpcLockMutex / sysIpcTryLockMutex 实际是宏:USE_TELEMETRY 打开时展开为带 __FUNCTION__/__FILE__/__LINE__ 的 …Command 版本,用于把锁竞争上报到 Telemetry src/dev_ng/rage/base/src/system/ipc.h:40-50, src/dev_ng/rage/base/src/system/ipc.cpp:519-530。

1.2 线程创建流程#

  1. sysIpcCreateThread(func, closure, stackSize, prio, threadName, cpu, threadTtyName):若通过 sysIpcSetThreadPriorityOverrideCb 注册了覆盖回调且提供了 threadTtyName,先以 atStringHash(threadTtyName) 为键让游戏层改写优先级/亲和性 src/dev_ng/rage/base/src/system/ipc.cpp:1019-1024、src/dev_ng/rage/base/src/system/ipc.cpp:1067-1071。
  2. 闭包数据放进静态池 s_ThreadWrappers(Xenon/PS3 48 项,其他平台 128 项),由 s_ThreadWrapperToken 保护 src/dev_ng/rage/base/src/system/ipc.cpp:665-690。
  3. 新线程进入 sysIpcThreadWrapper:设置 g_CurrentThreadId、关闭非规格化浮点(ipcDisableDenormals),复制并归还 wrapper 数据,把创建者的分配器安装为本线程的 Current/Master/Container 分配器(这三者均为 TLS),设置线程名与栈范围,然后调用用户函数;返回后从 sysThreadRegistry 注销并清理 DIAG_CONTEXT src/dev_ng/rage/base/src/system/ipc.cpp:842-911, src/dev_ng/rage/base/src/system/memory.h:344-346。
  4. PC Final 构建中 sysIpcCreateThread 被宏改写,在调用点就把线程名替换为空串,避免名字字符串残留在二进制里 src/dev_ng/rage/base/src/system/ipc.h:193-197。

sysThreadRegistry(THREAD_REGISTRY 除 32 位 RGSC DLL 外恒为 1)登记线程以便在 RAG 中显示与调整优先级,并支持命令行批量改优先级(AdjustPriosBasedOnParams) src/dev_ng/rage/base/src/system/threadregistry.h:19-24, src/dev_ng/rage/base/src/system/threadregistry.h:57-78。

1.3 临界区、自旋锁、原子操作与消息队列#

原语关键点证据
sysCriticalSectionToken递归锁;内部存储 u64 m_Impl[5](64 位)/[4](Win32)/[3](PS3)/Orbis 为 pthread_mutex*;Win32 用 InitializeCriticalSectionAndSpinCount,默认自旋 1000src/dev_ng/rage/base/src/system/criticalsection.h:89-141、src/dev_ng/rage/base/src/system/criticalsection.cpp:51-78
sysCriticalSectionRAII 守卫,带 m_LockCount,支持 TryEnter、提前 Exitsrc/dev_ng/rage/base/src/system/criticalsection.h:160-226
SYS_CS_SYNC(cs)固定局部名 __cssync,刻意让同一作用域锁两次编译失败以防死锁src/dev_ng/rage/base/src/system/criticalsection.h:250-254
超时诊断PS3 非 Final 下 Lock 以 10 秒超时等待,超时打印持有者与调用栈,-breakondeadlock 且附加调试器时断点;Win32 在 __DEV 下把等待耗时累计到 TLS g_CritSecWaitTickssrc/dev_ng/rage/base/src/system/criticalsection.cpp:100-160
全局作用域未初始化保护Win32 Lock/TryLock/Unlock 在 m_Impl[0]==0 时直接返回(静态初始化顺序问题的兜底)src/dev_ng/rage/base/src/system/criticalsection.cpp:111-113
sysSpinLockToken / sysSpinLockvolatile unsigned + CAS;指数退避(8→1024 次空转),获取后/释放前 sys_lwsync();不可递归,适合低竞争src/dev_ng/rage/base/src/system/spinlock.h:41-131
sysInterlocked*Exchange/CompareExchange/Increment 等 32/64 位与指针重载,实现在 interlocked.inlsrc/dev_ng/rage/base/src/system/interlocked.h:19-61
sysMessageQueue<T,N,blockOnFull>环形 FIFO,m_NonEmpty 信号量 + 临界区;blockOnFull 时额外 m_NonFull 信号量src/dev_ng/rage/base/src/system/messagequeue.h:27-60
THREAD_SAFE_FUNCTION_SCOPE_STATIC双检锁 + g_ThreadSafeFunctionScopeStaticCS + 写屏障,替代当时编译器不保证线程安全的函数静态构造src/dev_ng/rage/base/src/system/ipc.h:331-408

其它同目录原语(只列存在与位置,未逐一展开):sysBarrier(src/dev_ng/rage/base/src/system/barrier.h:29)、sysLockFreeRing(src/dev_ng/rage/base/src/system/lockfreering.h:17)、sysThreadPool(src/dev_ng/rage/base/src/system/threadpool.h:25)、sysNamedPipe(src/dev_ng/rage/base/src/system/namedpipe.h:23,被 bank/RAG 用作 TCP 管道)。

1.4 TLS 与 sysThreadType#

__THREAD 宏在 MSVC 为 __declspec(thread),PPU/SNC 为 __thread,Orbis 为 __thread + initial-exec TLS 模型,SPU/工具构建下为空 src/dev_ng/rage/base/src/system/tls.h:20-49。

sysThreadType 用 TLS 位掩码 g_ThisThreadType 标记“本线程的用途”:THREAD_TYPE_UPDATE(BIT0)、THREAD_TYPE_RENDER(BIT1)、THREAD_TYPE_PROCESS_MAIN(BIT2)、THREAD_TYPE_DEPENDENCY(BIT3),用户类型从 USER_THREAD_TYPE_SHIFT = 4 开始 src/dev_ng/rage/base/src/system/threadtype.h:14-47。注册点:

线程类型注册位置
PROCESS_MAIN + UPDATECommonMain_Prologue(主线程“直到被告知否则即为更新线程”)src/dev_ng/rage/base/src/system/main.cpp:692-698
DEPENDENCYsrc/dev_ng/rage/base/src/system/dependencyscheduler.cpp:291
RENDERsrc/dev_ng/rage/framework/src/fwrenderer/renderthread.cpp:1017、src/dev_ng/rage/framework/src/fwdrawlist/drawlistmgr.cpp:1338
游戏自定义类型src/dev_ng/game/system/system.cpp:1364、src/dev_ng/game/pathserver/PathServer.cpp:4475、src/dev_ng/game/vfx/particles/PtFxManager.cpp:3261

(上表注册位置由 rg "AddCurrentThreadType(" 得到。)bkManager::IsBankThread() 直接等价于 sysThreadType::IsUpdateThread() src/dev_ng/rage/base/src/bank/bkmgr.cpp:830-833。

1.5 挂起检测#

HANG_DETECT_THREAD 在非工具构建、且(PC 或 Orbis 或非 Final)时启用,提供 sysHangDetectTick/SaveZoneEnter/SaveZoneExit 等 API 与 AUTO_HANG_DETECT_CRASH_ZONE;Durango 非 Final 额外有挂起计时失败检测 src/dev_ng/rage/base/src/system/hangdetect.h:4-57。


2. 计时器#

2.1 sysTimer#

  • 原始计数类型 utimer_t:SPU 为 u32,其余 s64 src/dev_ng/rage/base/src/system/timer.h:41-46。
  • 频率:Xenon/PS3/PSP2/SPU 用编译期常量 CONST_FREQ(如 PPU 79.8 MHz 时基、Xenon 49.875 MHz);PC/Durango/Orbis 在首次构造 sysTimer 时查询频率填入 sysTimerConsts src/dev_ng/rage/base/src/system/timer.h:24-86, src/dev_ng/rage/base/src/system/timer.cpp:85-121。
  • GetTicks():Xenon __mftb、PPU SYS_TIMEBASE_GET、Orbis sceKernelReadTsc、Win32 QueryPerformanceCounter src/dev_ng/rage/base/src/system/timer.h:197-223,src/dev_ng/rage/base/src/system/timer.cpp:100-106。
  • GetSystemMsTime():PC 用 timeGetTime(),Durango 用 QPC 差值,Orbis 用 TSC;注释称约 1193 小时后才会溢出 src/dev_ng/rage/base/src/system/timer.h:150-157,src/dev_ng/rage/base/src/system/timer.cpp:45-78。
  • PC 专有 UpdateCpuSpeed():每秒以 timeGetTime 校准 QPC 换算系数,偏差超过 ±10% 时重写 sysTimerConsts(应对变频);-notimefix 关闭 src/dev_ng/rage/base/src/system/timer.cpp:124-197。
  • 非 Final(或 Final Logging)下 sm_StallThreshold = 2000 ms 用于“POSSIBLE STALL”提示 src/dev_ng/rage/base/src/system/timer.cpp:39-41。

2.2 sysTimeManager(TIME 宏)#

全局单例,TIME.Update() 每帧调用一次,提供 GetSeconds/GetInvSeconds/GetFrameCount,支持 REALTIME 与 FIXEDFRAME 两种模式、时间扭曲 m_TimeWarp、回放时间(m_UnwarpedReplaySeconds 等) src/dev_ng/rage/base/src/system/timemgr.h:17-65, src/dev_ng/rage/base/src/system/timemgr.h:218-263。游戏主循环实际使用的是 framework 层的 fwTimer(见 04-rage-framework/framework-systems.md)。


3. 平台抽象与构建标识#

system/platform.h 定义平台单字节代码 platform::WIN32PC='w'、XENON='x'、PS3='c'、PSP2='v'、WIN64PC='y'、ORBIS='o'、DURANGO='d',以及 RSG_PLATFORM / RSG_PLATFORM_ID(如 "ps4"、"xboxone"、"x64") src/dev_ng/rage/base/src/system/platform.h:12-65。 RSG_CONFIGURATION 按 __OPTIMIZED/__DEV/__BANK/__PROFILE/__FINAL 推导出 Debug/Beta/BankRelease/Profile/Final/Release 及短代码 src/dev_ng/rage/base/src/system/platform.h:67-98。

g_sysPlatform 是“目标平台”:在 Win32 PC 上它是可写变量(资源编译器/工具可以为别的平台生成数据),在主机上是常量;sysGetByteSwap / sysIsBigEndian(Xenon、PS3 为大端)/ sysIs64Bit(WIN64PC、ORBIS、DURANGO)据此决定字节交换与指针宽度 src/dev_ng/rage/base/src/system/platform.h:103-129, src/dev_ng/rage/base/src/system/platform.cpp:15-80。这一点直接影响资源构建(见 paging-and-resources.md)与 PSO 字节交换(见 parser-and-metadata.md)。

system/main.h 是被游戏顶层 .cpp 包含的“启动样板头”,其注释集中描述了响应文件、平台前缀参数、SET_DEFAULT_RESPONSE_FILE、PS3 内存开销等约定 src/dev_ng/rage/base/src/system/main.h:53-110、src/dev_ng/rage/base/src/system/main.h:161。


4. 异常、崩溃与堆栈#

  • EXCEPTION_HANDLING = 非资源编译器/工具/游戏工具 且 非 __MASTER;BREAKPAD_ENABLED 仅在 PC __MASTER;Durango 走 BACKTRACE_DUMP_AND_UPLOAD_ENABLED src/dev_ng/rage/base/src/system/exception.h:10-17。
  • sysException 命名空间暴露 Init/IsEnabled/HasBeenThrown/Crash,以及 PreExceptionDisplay/PostExceptionDisplay/GameExceptionDisplay/ExceptionDisplayStackLine 等函数指针钩子供游戏层追加崩溃信息(例如脚本栈) src/dev_ng/rage/base/src/system/exception.h:34-81。
  • sysStack:CaptureStackTrace、PrintStackTrace、PrintStackTraceAllThreads、RegisterBacktrace(返回 u16 句柄,供内存跟踪器复用调用栈)、map 文件符号解析 ParseMapFileSymbol src/dev_ng/rage/base/src/system/stack.h:44-126。
  • PS3 上主程序逻辑跑在 WrapCommonMain 创建的子线程 "[RAGE] Main Thread" 中(以便异常处理器在原始线程上工作) src/dev_ng/rage/base/src/system/main.cpp:593-627。

崩溃上报与防篡改相关的游戏层逻辑见 23-debug-security/security-overview.md,本页不展开。


5. PARAM / XPARAM 命令行参数系统#

5.1 声明宏与剥离规则#

宏非 __NO_OUTPUT 构建__NO_OUTPUT(Final 且非 Final Logging)
PARAM(x,desc)::rage::sysParam PARAM_x(0,"x",false,desc)::rage::stripped_param PARAM_x:所有 Get 恒返回 false,Set 故意未实现以在编译期报错
XPARAM(x)extern sysParam PARAM_xextern stripped_param PARAM_x
NOSTRIP_PARAM / NOSTRIP_XPARAM普通 sysParam仍为真 sysParam(但无描述)
NOSTRIP_PC_PARAM同 NOSTRIP_PARAM仅 Win32 PC 保留,主机上退化为 PARAM
FPARAM(i,x,desc)位置参数(第 i 个非 - 参数)同左
REQ_PARAM(x,desc)必需参数,缺失则打印帮助并退出CompileTimeAssert(0),Final 不可用
NOSTRIP_FINAL_LOGGING_PARAM__FINAL_LOGGING 时等于 NOSTRIP_PARAM否则等于 PARAM

证据:src/dev_ng/rage/base/src/system/param.h:24-104。

sysParam 构造函数把自身插入静态单链表头 sm_First,因此所有全局 PARAM 在静态初始化阶段完成“注册”,无需显式注册表 src/dev_ng/rage/base/src/system/param.cpp:151-165。对象只存 m_Name/m_Desc/m_Value/m_Index/m_Next/m_Required,取值 API 为 Get()(是否出现)、Get(const char*&)、GetParameter(仅当带值)、Get(int/u32/float&)、GetDebug、GetArray(逗号分隔)、Set(指针必须指向静态存储) src/dev_ng/rage/base/src/system/param.h:111-371。

5.2 启动时序#

图证据:CommonMain_Prologue 依次设置线程 ID/类型、初始化 TCP/RFS 设备、调用 ResponseFile 再调用 sysParam::Init src/dev_ng/rage/base/src/system/main.cpp:674-786; ResponseFile 逻辑 src/dev_ng/rage/base/src/system/main.cpp:351-433; diagChannel::InitArgs src/dev_ng/rage/base/src/diag/channel.cpp:954-1005。

5.3 响应文件#

  • 编译条件 !__FINAL || RSG_PC,即 PC Final 也支持 src/dev_ng/rage/base/src/system/main.cpp:269。
  • 来源优先级:命令行第一个存在的 @path → g_DefaultResponseFile(Xenon game:\commandline.txt;PS3/Orbis/PC Final commandline.txt;Durango xg:\commandline.txt,Final Logging 时 g:\;PC 非 Final 默认无)→ PS3/Orbis 还会尝试 EBOOT.BIN 同目录 → 否则读取环境变量 RAGE_COMMON_ARGS(PC Final 禁用) src/dev_ng/rage/base/src/system/main.cpp:351-433, src/dev_ng/rage/base/src/system/defaultresponsefile.cpp:9-29。
  • 解析:空白分隔、双引号包裹、;/#/// 行注释;嵌套 @file 递归展开(PC Final 禁止嵌套);文件大小上限 ARGV_HEAP_SIZE(PC 512 KB,其它 8 KB),参数个数上限 ARGV_HEAP_SIZE/16;原命令行中非 @ 参数追加在末尾 src/dev_ng/rage/base/src/system/main.cpp:278-347,src/dev_ng/rage/base/src/system/main.cpp:394-413。

5.4 sysParam::Init 匹配规则#

src/dev_ng/rage/base/src/system/param.cpp:425-620:

  1. 只在 !__NO_OUTPUT || RSG_PC 时解析(PC Final 也解析命令行)。
  2. 修正 Outlook (0x96) 与 UTF-8 长破折号前缀。
  3. -help[=section] 打印帮助后 Quitf。帮助的分节来自描述开头的 [section] src/dev_ng/rage/base/src/system/param.cpp:180-206。
  4. 平台/配置前缀过滤:-pc:、-p3:、-xe:、-du:、-or:,以及配置 -de:(非优化)、-be:(优化+DEV)、-br:(BANK)、-re:(非 DEV)、-x8:/-x6:(32/64 位);前缀可串联、可用 ! 取反;不匹配时把参数首字符改为 X 使其失效;未知前缀 Quitf(ERR_SYS_PARAM_PREFIX)。
  5. 名称比较用 stricmps:大小写不敏感,并且忽略任一方末尾的 s(-texture 可匹配 textures) src/dev_ng/rage/base/src/system/param.cpp:379-405。
  6. 取值:-name=value 或 -name value(下一个参数不以 - 开头时);-name="..." 形式被拒绝(ERR_SYS_PARAM_FORMAT)。
  7. -nofoo 会清除已有的 foo,-foo 也会清除名为 nofoo 的参数(第二遍匹配)。
  8. 未识别参数给出警告,但以 sc_ 开头(仅供脚本读取的参数)或包含 _tty/_popup/_log/_all/_rag(diag 通道参数)的不警告;-X 前缀用于静默屏蔽。
  9. 最后检查 REQ_PARAM,缺失则帮助 + 退出。

脚本侧可通过 sysParam::SearchArgArray / GetByName 读取任意参数(不需要对应的 sysParam 对象) src/dev_ng/rage/base/src/system/param.h:269-288。在 sysParam 尚未建立前(例如堆尺寸参数),非 Final 代码用 FindCommandLineArg/GetCommandLineArgInt 直接扫描原始命令行 src/dev_ng/rage/base/src/system/param.cpp:28-145。


6. diag:通道、日志与断言#

6.1 全局断言与输出宏(forceinclude 定义)#

Assert/Assertf/Verifyf/FatalAssert/AssertMsg/FastAssert 定义在每个配置的 forceinclude 头中(以 win64_beta.h 为例):Assertf(x,fmt,...) 展开为 Likely(x) || diagAssertHelper(__FILE__,__LINE__,…) || (__debugbreak(),0);__ASSERT==0 时 Assert 系列为空、Verifyf 只求值条件 src/dev_ng/rage/base/src/forceinclude/win64_beta.h:255-319。 __NO_OUTPUT = __FINAL && !__FINAL_LOGGING;此时 Displayf/Warningf/Errorf/Debugf* 为空,Quitf 在主机上为 __debugbreak()、在 PC 上为 QuitNoOutput(errorCode) src/dev_ng/rage/base/src/forceinclude/win64_beta.h:482-514。各配置具体取值见 00-overview/build-and-config.md。

6.2 diagChannel 模型#

通道是分层的日志命名空间。每个模块在一个公共头中 RAGE_DECLARE_CHANNEL(Tag) 并定义 xxxAssertf/xxxErrorf/xxxDisplayf/... 包装宏,在一个 .cpp 中 RAGE_DEFINE_CHANNEL(Tag) 定义全局对象 Channel_Tag src/dev_ng/rage/base/src/diag/channel.h:12-73, src/dev_ng/rage/base/src/diag/channel.h:345-348。

  • 严重级别 diagSeverity:FATAL, ASSERT, ERROR, WARNING, DISPLAY, DEBUG1, DEBUG2, DEBUG3(数值越小越严重) src/dev_ng/rage/base/src/diag/channel.h:101。
  • 每个通道有三个阈值 FileLevel(默认 DEBUG1)、TtyLevel(默认 DISPLAY)、PopupLevel(默认 ASSERT),MaxLevel 为三者最大值,宏在格式化前用 severity <= MaxLevel 剔除消息以省去 formatf 开销 src/dev_ng/rage/base/src/diag/channel.h:186-197,src/dev_ng/rage/base/src/diag/channel.h:340-343。
  • 子通道 RAGE_DEFINE_SUBCHANNEL(parent,tag) 生成 Channel_parent_tag,diagChannel::ALL 是所有通道的根 src/dev_ng/rage/base/src/diag/channel.h:238。
  • .cpp 内 #define __rage_channel xxx 后可使用 rageDebugf1/rageAssertf 等通用宏 src/dev_ng/rage/base/src/diag/channel.h:383-410。
  • RAGE_LOG_FMT 可在单个 .cpp 中重定义以给所有输出加前缀;因 unity 构建,它刻意放在 include guard 之外每次重置 src/dev_ng/rage/base/src/diag/channel.h:350-423。
  • 构建差异:__NO_OUTPUT 下全部通道宏为空(RAGE_VERIFY 仍求值,FATAL 日志仍调用 diagTerminate);SPU 下退化为全局 Displayf 等;Xenon __DEV 下 DISABLE_DEBUG_CHANNELS 去掉 Debug1-3 以缩小二进制(注释特别注明为 GTA5) src/dev_ng/rage/base/src/diag/channel.h:83-184。

6.3 命令行控制#

diagChannel::InitArgs 扫描参数中的 -<ParamTag>_log=、_tty=、_popup(s)=、_all=,把级别写入匹配通道及其所有子通道;popup 级别只接受不低于 ERROR 的严重度 src/dev_ng/rage/base/src/diag/channel.cpp:954-1005。 Channel_all/_log/_tty/_popups/_rag 这几个 PARAM 只是为了让 -help 显示说明的占位符 src/dev_ng/rage/base/src/diag/channel.cpp:246-250。其它常用 diag 参数:-nopopups[=abort|retry|ignore]、-noquits、-noquitonassert、-usevsoutput、-logtimestamps、-ttyframeprefix、-redirectTTY、-bugAsserts src/dev_ng/rage/base/src/diag/channel.cpp:79-92,src/dev_ng/rage/base/src/diag/channel.cpp:253-259,src/dev_ng/rage/base/src/diag/channel.cpp:1022-1024。

6.4 diagLogDefault 处理路径#

src/dev_ng/rage/base/src/diag/channel.cpp:1742-2140:

  1. 计算 logToFile/logToTty/logToPopup;若为 popup 级别且 (file<<32|line) 在已忽略列表中则立即返回(即 Assert 弹窗中点 Ignore 后同位置不再提示)。
  2. 用 512 字节缓冲 CHANNEL_MESSAGE_SIZE 格式化消息(超出部分被截断)src/dev_ng/rage/base/src/diag/channel.h:78-81。
  3. 严重度达到 popup 级别时先打印程序名与调用栈(diagPrintStackTrace)。
  4. 写文件:[相对ms:帧号] 前缀 线程名 通道Tag 消息,带 DIAG_CONTEXT_MESSAGE 上下文链;ASSERT 及以上立即 Flush。
  5. 写 TTY;REDIRECT_TTY 时可经 TCP 发送到 R*TM(1 MB 环形缓冲 s_TtyBuffer)src/dev_ng/rage/base/src/diag/channel.cpp:543-552。
  6. PC 上 FATAL 可发邮件(-emailasserts)。
  7. 调用 diagExternalCallback(游戏可修改严重度或自行弹窗),再按需通过 fiRemoteShowMessageBox 弹出 Abort/Retry/Ignore 对话框(经 RFS 显示在开发 PC 上);-bugAsserts 时使用含“Request AssertBot Popup”的自定义按钮。

日志文件由 diagChannel::OpenLogFile 打开并启动 "[RAGE] Logfile" 线程(8 KB 栈、PRIO_ABOVE_NORMAL),该线程每秒 flush,并在超过 s_maxLogFileSize 时滚动为 name_N.ext 并调用新文件回调 src/dev_ng/rage/base/src/diag/channel.cpp:1114-1226。

6.5 其它 diag 组件#

组件职责证据
diagOutput全局输出掩码、警告/错误计数、VS 格式输出src/dev_ng/rage/base/src/diag/output.h:55-67
diagContextMessage / DIAG_CONTEXT_MESSAGE__BANK 下的按线程上下文栈,日志/弹窗附加“...from X”src/dev_ng/rage/base/src/diag/output.h:71-95
diagTracker / RAGE_TRACKRAGE_TRACKING = !__FINAL && !__SPU;分配跟踪器抽象(Push/Pop/Tally/UnTally/InitHeap/Report),diagTrackerRemote2 把数据发到外部内存可视化工具(-memvisualize)src/dev_ng/rage/base/src/diag/tracker.h:14-42、src/dev_ng/rage/base/src/diag/tracker.h:114-246、src/dev_ng/rage/base/src/system/main.cpp:789-872
restService*基于内嵌 civetweb 的 REST 调试服务目录(含 bank 与文件访问服务)src/dev_ng/rage/base/src/diag/restservice.h:46-192
debuglog回放/确定性调试用的二进制日志(DEBUGLOG_ONLY)src/dev_ng/rage/base/src/diag/debuglog.h:23-60
errorcodes.hFatalErrorCode 枚举,Quitf(code, …) / diagTerminate(code, silent) 使用src/dev_ng/rage/base/src/diag/errorcodes.h:13、src/dev_ng/rage/base/src/diag/channel.h:75-76
XmlLog简单 XML 日志(文件/stdout/字符串)src/dev_ng/rage/base/src/diag/xmllog.h:19-95

7. profile:统计、timebar、Rocky、Telemetry#

RAGE 里同时存在四套性能剖析机制,开关各不相同:

机制开关用法数据去向
pf 元素(pfTimer/pfValue/pfCounter,pfGroup/pfPage)__STATS = (__DEV && !__XENON) && !__TOOL && !__SPU && !__RGSC_DLL src/dev_ng/rage/base/src/diag/stats.h:12-23PF_PAGE → PF_GROUP → PF_LINK → PF_TIMER(name,group) / PF_START/PF_STOP/PF_FUNCpfProfiler(GetRageProfiler()),由 grprofile 在屏幕或 RAG 显示
TimebarRAGE_TIMEBARS = (!__FINAL || __FINAL_LOGGING) && !__PROFILE && !__TOOL;INCLUDE_DETAIL_TIMEBARS = __DEV || __BANKPF_PUSH_TIMEBAR/PF_POP_TIMEBAR/PF_START_TIMEBAR/PF_AUTO_PUSH_TIMEBAR、*_DETAIL、*_BUDGETED、启动条 PF_*_STARTUPBAR函数指针 pfTimebarFuncs::g_*Cb,core 中为空操作,由 grprofile/timebars.cpp 安装真实实现
RockyUSE_PROFILER = RSG_PC ? ROCKY_LEVEL_NORMAL : 0(Final/工具构建为 0)PROFILE、PROFILER_EVENT(name)、PROFILER_CATEGORY、PROFILER_FRAME、PROFILER_TAG、GPU 事件线程本地事件缓冲,经 Profiler::Server 监听 TCP 端口(从 port 起尝试 range 个)发给外部查看器
TelemetryUSE_TELEMETRY = !RSG_PS3 && !__FINAL && !__RESOURCECOMPILERTELEMETRY_START_ZONE/END_ZONE/ALLOC/FREE/TRY_LOCK...TelemetryCallbacks 函数表,由 rage/suite/src/telemetry/telemetry_rage.cpp 调用 SetTelemetryCallbacks 安装

证据:pf 宏 src/dev_ng/rage/base/src/profile/element.h:28-120、 src/dev_ng/rage/base/src/profile/group.h:48-83、 src/dev_ng/rage/base/src/profile/page.h:51-59、 src/dev_ng/rage/base/src/profile/profiler.h:151-160; timebar src/dev_ng/rage/base/src/profile/timebars.h:18-124、真实回调安装 src/dev_ng/rage/base/src/grprofile/timebars.cpp:2108; Rocky src/dev_ng/rage/base/src/profile/settings.h:13-68、src/dev_ng/rage/base/src/profile/rocky.h:1412-1571、src/dev_ng/rage/base/src/profile/profiler_server.cpp:47、src/dev_ng/rage/base/src/profile/profiler_server.cpp:234; Telemetry src/dev_ng/rage/base/src/profile/telemetry.h:1-80、src/dev_ng/rage/base/src/profile/telemetry.cpp:30、src/dev_ng/rage/suite/src/telemetry/telemetry_rage.cpp:285。

要点:

  • Rocky 事件时间戳以 Timestamp::Pack(sysTimer::GetTicks()) 压缩为 32 位,EventStorage 为每类 tag(float/u32/vec3/string/drawcall…)维护独立缓冲 src/dev_ng/rage/base/src/profile/rocky.h:529-562,src/dev_ng/rage/base/src/profile/rocky.h:912-974。
  • 只有 PC 编译 Rocky;主机分析依赖 timebar、平台工具(Razor/PIX,system/pix.h)与 Telemetry。
  • profile 目录中还有 PS3 SPURS 跟踪(cellspurstrace.*)、Durango ETW(event_tracer.cpp)、stalldetection.*、stream_profiler.* 等平台性专项,本页未逐一展开。

8. bank:调试控件与 RAG 协议#

8.1 控件树#

bank 仅在 __BANK && !__SPU 下编译。bkWidget 是所有控件的基类,通过 m_Next/m_Parent/m_Child 构成树,可选的 ExtraData 存回调 datCallback、tooltip、颜色、只读标志;每个控件有类型 GUID(BKGUID(a,b,c,d) 四字符码)和远程 ID 映射节点 m_RemoteToLocalNode src/dev_ng/rage/base/src/bank/widget.h:11-15, src/dev_ng/rage/base/src/bank/widget.h:41-308。子类需实现 RemoteCreate()(在 RAG 侧创建对应控件),可选 RemoteUpdate/RemoteDestroy;Win32 PC 另有本地窗口实现(WindowCreate/WindowMessage) src/dev_ng/rage/base/src/bank/widget.h:243-265。

bkGroup 提供 AddButton/AddToggle/AddSlider/AddText/AddCombo/AddColor/... 等工厂方法,bkBank : bkGroup 是一个顶层窗口,bkManager : bkGroup 是单例根(BANKMGR),CreateBank(name) 创建银行 src/dev_ng/rage/base/src/bank/group.h:137-203, src/dev_ng/rage/base/src/bank/bank.h:53-80, src/dev_ng/rage/base/src/bank/bkmgr.h:35-99, src/dev_ng/rage/base/src/bank/bkmgr.h:345。

8.2 帧内更新#

bkManager::Update()(应在更新线程每帧调用):发送延迟的 PING 应答 → 执行其它线程经 bkManager::Invoke 排队的委托 → bkRemotePacket::ReceivePackets() → bkGroup::Update()(递归更新控件,必要时产生 CHANGED 包)→ bkRemotePacket::SendPackets() → 处理本地手柄输入(无 RAG 时的屏上菜单) src/dev_ng/rage/base/src/bank/bkmgr.cpp:497-540, src/dev_ng/rage/base/src/bank/bkmgr.cpp:835-845。因此控件回调运行在更新线程,其它线程修改 bank 应通过 Invoke。

8.3 RAG 通信协议概要#

图证据:握手与版本检查 src/dev_ng/rage/base/src/bank/packet.cpp:277-420,RAG_VERSION 2.0f src/dev_ng/rage/base/src/bank/packet.cpp:66;端口偏移枚举与握手端口 src/dev_ng/rage/base/src/bank/packet.h:276-304; bank/output 管道连接 src/dev_ng/rage/base/src/bank/packet.cpp:477、src/dev_ng/rage/base/src/bank/packet.cpp:511;收包分发 src/dev_ng/rage/base/src/bank/packet.cpp:562-608;发包压缩 src/dev_ng/rage/base/src/bank/packet.cpp:610-665。

协议细节:

  • 包头 12 字节 {u16 m_Length; u16 m_Command; int m_Guid; u32 m_Id},单包载荷最多 500 字节(DATA_SIZE = 512 - HEADER_SIZE) src/dev_ng/rage/base/src/system/pipepacket.h:20-26,src/dev_ng/rage/base/src/system/pipepacket.h:225-228。
  • 命令:CREATE, DESTROY, CHANGED, FILL_COLOR, READ_ONLY, USER;DESTROY 统一处理,其余按 m_Guid 在 sm_Types(最多 45 个注册类型)中查 handler src/dev_ng/rage/base/src/bank/packet.h:104-141。
  • 控件 ID:bkRemoteWidgetMap 以递增 u32 分配(0 为无效),溢出后才搜索空闲 ID src/dev_ng/rage/base/src/bank/packet.h:28-88。
  • 压缩工厂注册 fiCompressionData、datCompressionData、sysNoCompressionData 三种格式 src/dev_ng/rage/base/src/bank/packet.cpp:258-263。
  • 写超时 10 秒(无调试器时),失败则 ForceCloseConnection src/dev_ng/rage/base/src/bank/packet.cpp:639-651。
  • 相关参数:-socketbaseport、-ragConnectTimeout、-ragnotty、-bankpipename、-waitforpipe src/dev_ng/rage/base/src/bank/packet.cpp:93-119。

RAG 端(C# 工具)位于 rage/base/tools,其结构见 24-toolchain/rage-tools-and-rag.md。diag 通道可经 -<channel>_rag 在 RAG 中建立输出窗格,parser 可经 visitorwidgets 自动为 parStructure 生成控件(见 parser-and-metadata.md)。


9. 其它 base 模块(简述)#

模块内容证据
output输出设备抽象:ioAcutatorDevice(拼写如此;重/轻震动马达、扳机震动、力反馈轴)、ioLightDevice(左/中/右灯源,手柄灯条与 Logitech 键盘灯)及效果类 rumbleeffect、constantlighteffect、flashinglighteffectsrc/dev_ng/rage/base/src/output/actuatordevice.h:12-80、src/dev_ng/rage/base/src/output/lightdevice.h:14-69
system/memory.hsysMemAllocator 接口:Allocate(size,align,heapIndex)、TryAllocate、Free、DeferredFree、AllocateMap(datResourceMap&);TLS Current/Master/Container;eMemoryType(GAME/RESOURCE × VIRTUAL/PHYSICAL、DEBUG)src/dev_ng/rage/base/src/system/memory.h:273-286、src/dev_ng/rage/base/src/system/memory.h:404-633;拓扑见 01-runtime/memory-and-pools.md
system 杂项sysBootManager、sysService(平台挂起/恢复事件)、sysUserList(Durango 用户)、sysEndian、sysObfuscatedTypes、tmcommands(R*TM 目标管理器命令)、ETW(ETW_SUPPORTED 当前恒为 0)src/dev_ng/rage/base/src/system/bootmgr.h:30、src/dev_ng/rage/base/src/system/service.h:43-137、src/dev_ng/rage/base/src/system/userlist.h:32-61、src/dev_ng/rage/base/src/system/endian.h:23、src/dev_ng/rage/base/src/system/obfuscatedtypes.h:15、src/dev_ng/rage/base/src/system/eventtracingforwindows.h:10

约束与边界行为#

  • 线程句柄必须 sysIpcWaitThreadExit,否则在 Win32 上泄漏句柄;PS3/POSIX 信号量与互斥量来自固定大小静态池(1024 / 128),耗尽时 Errorf 并返回 NULL。
  • SYS_CS_SYNC 固定变量名:同一作用域不能同时持有两个临界区守卫——这是有意的防死锁约束。
  • sysCriticalSectionToken 在 Win32 上若作为全局对象尚未构造即被使用,Lock 静默跳过(不加锁);依赖静态初始化顺序的代码需注意。
  • PARAM 在 __NO_OUTPUT 下被剥离,Final 代码中调用 PARAM_x.Set() 会编译失败;必须使用 NOSTRIP_PARAM。PC Final 仍然解析命令行与 commandline.txt,但禁止嵌套响应文件与 RAGE_COMMON_ARGS。
  • diag 消息被截断到 512 字节;Assert 被“Ignore”后按 (file 指针, line) 记住,因此同一行即使条件不同也不再弹出。
  • bank 回调在更新线程执行;bkManager::Invoke 队列满时只 Errorf,委托会丢失。

未决问题 / 证据不足#

  • sysIpcPriority 注释“lower is more important”与枚举值方向相反,未逐平台核对优先级映射函数。
  • profile/ 中的 SPURS/ETW/stall detection、diag/restservice 的具体服务注册点、system/task_* 与 dependency*(见 01-runtime)未在本页展开。
  • init/ 目录实际是 INI 解析器 initIni(src/dev_ng/rage/base/src/init/ini.h:27-91),并非“模块初始化”;仓库中检索到的使用者主要是 DCC/CLI 工具,在 game/ 中未确认使用(只做了有限 rg)。
  • 断言宏只核对了 win64_beta.h;其他平台/配置文件存在差异(如 RAGE_MINIMAL_ASSERTS 仅 Xenon 优化构建),以 00-overview/build-and-config.md 为准。