system 与诊断:线程/同步、计时、平台抽象、diag、PARAM、profile、bank
本页解释 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。
相关源码文件#
| 区域 | 关键文件 |
|---|---|
| 线程/IPC | system/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(断言宏样例) |
| profile | profile/element.h、profile/group.h、profile/page.h、profile/timebars.h、profile/settings.h、profile/rocky.h、profile/telemetry.h、profile/profiler_server.cpp |
| bank | bank/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/DeleteSema | Wait/Signal 可带 count;sysIpcInfinite = 0xFFFFFFFF | Win32 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-128 | Win32 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 线程创建流程#
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。- 闭包数据放进静态池
s_ThreadWrappers(Xenon/PS3 48 项,其他平台 128 项),由s_ThreadWrapperToken保护src/dev_ng/rage/base/src/system/ipc.cpp:665-690。 - 新线程进入
sysIpcThreadWrapper:设置g_CurrentThreadId、关闭非规格化浮点(ipcDisableDenormals),复制并归还 wrapper 数据,把创建者的分配器安装为本线程的Current/Master/Container分配器(这三者均为 TLS),设置线程名与栈范围,然后调用用户函数;返回后从sysThreadRegistry注销并清理DIAG_CONTEXTsrc/dev_ng/rage/base/src/system/ipc.cpp:842-911,src/dev_ng/rage/base/src/system/memory.h:344-346。 - 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,默认自旋 1000 | src/dev_ng/rage/base/src/system/criticalsection.h:89-141、src/dev_ng/rage/base/src/system/criticalsection.cpp:51-78 |
sysCriticalSection | RAII 守卫,带 m_LockCount,支持 TryEnter、提前 Exit | src/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_CritSecWaitTicks | src/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 / sysSpinLock | volatile unsigned + CAS;指数退避(8→1024 次空转),获取后/释放前 sys_lwsync();不可递归,适合低竞争 | src/dev_ng/rage/base/src/system/spinlock.h:41-131 |
sysInterlocked* | Exchange/CompareExchange/Increment 等 32/64 位与指针重载,实现在 interlocked.inl | src/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 + UPDATE | CommonMain_Prologue(主线程“直到被告知否则即为更新线程”)src/dev_ng/rage/base/src/system/main.cpp:692-698 |
| DEPENDENCY | src/dev_ng/rage/base/src/system/dependencyscheduler.cpp:291 |
| RENDER | src/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,其余s64src/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时查询频率填入sysTimerConstssrc/dev_ng/rage/base/src/system/timer.h:24-86,src/dev_ng/rage/base/src/system/timer.cpp:85-121。 GetTicks():Xenon__mftb、PPUSYS_TIMEBASE_GET、OrbissceKernelReadTsc、Win32QueryPerformanceCountersrc/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 = 2000ms 用于“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_ENABLEDsrc/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 文件符号解析ParseMapFileSymbolsrc/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_x | extern 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(Xenongame:\commandline.txt;PS3/Orbis/PC Finalcommandline.txt;Durangoxg:\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:
- 只在
!__NO_OUTPUT || RSG_PC时解析(PC Final 也解析命令行)。 - 修正 Outlook (
0x96) 与 UTF-8 长破折号前缀。 -help[=section]打印帮助后Quitf。帮助的分节来自描述开头的[section]src/dev_ng/rage/base/src/system/param.cpp:180-206。- 平台/配置前缀过滤:
-pc:、-p3:、-xe:、-du:、-or:,以及配置-de:(非优化)、-be:(优化+DEV)、-br:(BANK)、-re:(非 DEV)、-x8:/-x6:(32/64 位);前缀可串联、可用!取反;不匹配时把参数首字符改为X使其失效;未知前缀Quitf(ERR_SYS_PARAM_PREFIX)。 - 名称比较用
stricmps:大小写不敏感,并且忽略任一方末尾的s(-texture可匹配textures)src/dev_ng/rage/base/src/system/param.cpp:379-405。 - 取值:
-name=value或-name value(下一个参数不以-开头时);-name="..."形式被拒绝(ERR_SYS_PARAM_FORMAT)。 -nofoo会清除已有的foo,-foo也会清除名为nofoo的参数(第二遍匹配)。- 未识别参数给出警告,但以
sc_开头(仅供脚本读取的参数)或包含_tty/_popup/_log/_all/_rag(diag 通道参数)的不警告;-X前缀用于静默屏蔽。 - 最后检查
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:
- 计算
logToFile/logToTty/logToPopup;若为 popup 级别且(file<<32|line)在已忽略列表中则立即返回(即 Assert 弹窗中点 Ignore 后同位置不再提示)。 - 用 512 字节缓冲
CHANNEL_MESSAGE_SIZE格式化消息(超出部分被截断)src/dev_ng/rage/base/src/diag/channel.h:78-81。 - 严重度达到 popup 级别时先打印程序名与调用栈(
diagPrintStackTrace)。 - 写文件:
[相对ms:帧号] 前缀 线程名 通道Tag 消息,带DIAG_CONTEXT_MESSAGE上下文链;ASSERT 及以上立即Flush。 - 写 TTY;
REDIRECT_TTY时可经 TCP 发送到 R*TM(1 MB 环形缓冲s_TtyBuffer)src/dev_ng/rage/base/src/diag/channel.cpp:543-552。 - PC 上 FATAL 可发邮件(
-emailasserts)。 - 调用
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_TRACK | RAGE_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.h | FatalErrorCode 枚举,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-23 | PF_PAGE → PF_GROUP → PF_LINK → PF_TIMER(name,group) / PF_START/PF_STOP/PF_FUNC | pfProfiler(GetRageProfiler()),由 grprofile 在屏幕或 RAG 显示 |
| Timebar | RAGE_TIMEBARS = (!__FINAL || __FINAL_LOGGING) && !__PROFILE && !__TOOL;INCLUDE_DETAIL_TIMEBARS = __DEV || __BANK | PF_PUSH_TIMEBAR/PF_POP_TIMEBAR/PF_START_TIMEBAR/PF_AUTO_PUSH_TIMEBAR、*_DETAIL、*_BUDGETED、启动条 PF_*_STARTUPBAR | 函数指针 pfTimebarFuncs::g_*Cb,core 中为空操作,由 grprofile/timebars.cpp 安装真实实现 |
| Rocky | USE_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 个)发给外部查看器 |
| Telemetry | USE_TELEMETRY = !RSG_PS3 && !__FINAL && !__RESOURCECOMPILER | TELEMETRY_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 个注册类型)中查 handlersrc/dev_ng/rage/base/src/bank/packet.h:104-141。 - 控件 ID:
bkRemoteWidgetMap以递增u32分配(0 为无效),溢出后才搜索空闲 IDsrc/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 秒(无调试器时),失败则
ForceCloseConnectionsrc/dev_ng/rage/base/src/bank/packet.cpp:639-651。 - 相关参数:
-socketbaseport、-ragConnectTimeout、-ragnotty、-bankpipename、-waitforpipesrc/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、flashinglighteffect | src/dev_ng/rage/base/src/output/actuatordevice.h:12-80、src/dev_ng/rage/base/src/output/lightdevice.h:14-69 |
system/memory.h | sysMemAllocator 接口: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 为准。