会话状态机:CGameSessionStateMachine
CGameSessionStateMachine 管理“游戏会话”之间的切换:首次启动时的平台检查(PC 的 Social Club/Steam/权益、主机的登录检查)、档案加载、语言选择、伽马校准、邀请检查、开局自动读档、落地页,以及运行期的新游戏、手动读档、换关、单人/多人切换。它决定何时调用 CGame::InitLevel/InitSession/ShutdownSession/ShutdownLevel,因此是 game-skeleton-and-main-loop.md 中 LEVEL/SESSION 两类 skeleton 模式的唯一驱动者。
一个重要特征:这个状态机的大多数状态是同步阻塞的——在一次 Update() 调用中自行循环(sysIpcSleep(33) + 手动“泵”少量系统),直到对话框、存档枚举、权益检查或整个关卡加载完成为止;只有淡出等待类状态跨帧推进。
背景与相关页面#
CApp在首帧RunGame_OnEnter中第一次驱动本状态机:startup-and-app-fsm.md- 每个状态最终调用的 skeleton 模式与系统清单:game-skeleton-and-main-loop.md
- 存档读写、自动读档的数据侧:../19-persistence-replay/save-load.md
- 落地页、加载画面、引导流程 UI:../18-frontend-ui/frontend-flows.md
- PC 权益、Steam/Epic、Social Club:../22-input-platform/platform-services-and-settings.md、../16-network-online/rline-online-services.md
- 网络会话与过渡(多人模式的会话层,与本页的“游戏会话”不同):../16-network-online/network-sessions-and-transitions.md
相关源码文件#
| 文件 | 作用 |
|---|---|
src/dev_ng/game/Core/gamesessionstatemachine.h | 状态枚举、公有 API、私有 Process* 函数与静态成员 |
src/dev_ng/game/Core/gamesessionstatemachine.cpp | 全部状态处理、Update 循环、SetState 守卫、泵函数 |
src/dev_ng/game/Core/game.h/.cpp | CGame 对状态机的静态包装(StartNewGame/LoadSaveGame/ChangeLevel/...)与每帧 Update 调用 |
src/dev_ng/game/frontend/landing_page/LandingPage.cpp、LandingPageArbiter.cpp、LandingPageConfig.h | 落地页进入/退出请求与开关 |
src/dev_ng/game/network/Live/livemanager.cpp | 登录状态变化 → CGame::HandleSignInStateChange |
1. 角色与驱动方式#
- 类的所有成员都是
static;CGame持有一个实例并以静态函数转发src/dev_ng/game/Core/gamesessionstatemachine.h:267-276、src/dev_ng/game/Core/game.h:57-74。 CGame::Init调用Init()设定首个状态src/dev_ng/game/Core/game.cpp:1110;CApp::RunGame_OnEnter调用第一次Update()(整个引导流程在这里同步完成)src/dev_ng/game/Core/app.cpp:1207;此后CGame::Update每帧调用一次src/dev_ng/game/Core/game.cpp:3377。Update()是一个do { switch(m_State) ... } while (m_State != oldState)循环:一次调用内会连续处理状态,直到某个状态没有改变m_State为止;PC 上每次迭代先调CApp::CheckExit();断言构建检测超过MAX_STATE_CHANGES次切换的疑似死循环src/dev_ng/game/Core/gamesessionstatemachine.cpp:561-676。IsIdle()即m_State == IDLE;CGame::IsChangingSessionState()为其取反,并使ShouldDoFullUpdateRender()返回 false(切换期间只跑SIMPLE_MAIN_UPDATE)src/dev_ng/game/Core/gamesessionstatemachine.h:81、src/dev_ng/game/Core/game.h:63、src/dev_ng/game/Core/game.cpp:3194-3197。DEFAULT_START_LEVEL = 1,m_Level/m_ReInitLevel初值为它src/dev_ng/game/Core/gamesessionstatemachine.h:58-59、src/dev_ng/game/Core/gamesessionstatemachine.cpp:90-94。
2. 状态清单#
| 状态 | 编译条件 | 处理函数 | 阻塞? | 证据 |
|---|---|---|---|---|
IDLE | – | ProcessIdleState:若刚完成开局自动读档,复位自动读档状态 | 否 | src/dev_ng/game/Core/gamesessionstatemachine.cpp:678-686 |
INIT | – | ProcessInitState → CHECK_SIGN_IN_STATE | 否 | src/dev_ng/game/Core/gamesessionstatemachine.cpp:688-691 |
CHECK_FOR_RGSC | __WIN32PC | Social Club 是否初始化 | 否 | src/dev_ng/game/Core/gamesessionstatemachine.cpp:242-270 |
HANDLE_RGSC_FAILED | __WIN32PC | 错误对话框循环 | 是(无限循环直到选择) | src/dev_ng/game/Core/gamesessionstatemachine.cpp:272-384 |
REQUIRE_PC_ENTITLEMENT | __WIN32PC | 权益检查循环 | 是 | src/dev_ng/game/Core/gamesessionstatemachine.cpp:447-481 |
REINIT | __WIN32PC | 登录变化后重建会话 + 权益检查 | 是 | src/dev_ng/game/Core/gamesessionstatemachine.cpp:167-240 |
TWO_FACTOR_AUTH_CHECK | __WIN32PC(逻辑仅 RAGE_SEC_ENABLE_YUBIKEY) | YubiKey 双因素循环 | 是 | src/dev_ng/game/Core/gamesessionstatemachine.cpp:1062-1119 |
STEAM_VERIFICATION | __WIN32PC && __STEAM_BUILD | Steam 所有权验证循环 | 是 | src/dev_ng/game/Core/gamesessionstatemachine.cpp:386-444 |
START_AUTOLOAD | – | 决定是否尝试自动读档 | 否 | src/dev_ng/game/Core/gamesessionstatemachine.cpp:693-720 |
CHECK_SIGN_IN_STATE | – | 主机登录检查 | 是 | src/dev_ng/game/Core/gamesessionstatemachine.cpp:722-784 |
LOAD_PROFILE | – | 刷新档案设置 | 否 | src/dev_ng/game/Core/gamesessionstatemachine.cpp:786-796 |
CHOOSE_LANGUAGE | 非 PS3 | 语言选择/文本重载 | 可能重建加载画面 | src/dev_ng/game/Core/gamesessionstatemachine.cpp:798-846 |
GAMMA_CALIBRATION | – | 首次启动伽马校准 | 是 | src/dev_ng/game/Core/gamesessionstatemachine.cpp:848-869 |
CHECK_FOR_INVITE | – | 是否通过邀请启动 | 是 | src/dev_ng/game/Core/gamesessionstatemachine.cpp:871-912 |
ENUMERATE_SAVED_GAMES | – | 枚举存档并决定读档还是新游戏 | 是 | src/dev_ng/game/Core/gamesessionstatemachine.cpp:914-959 |
AUTOLOAD_SAVE_GAME | – | 读取最新存档并加载关卡/会话 | 是 | src/dev_ng/game/Core/gamesessionstatemachine.cpp:961-1060 |
START_NEW_GAME | – | 淡出 → 重建会话(必要时重建关卡) | 淡出跨帧,加载阻塞 | src/dev_ng/game/Core/gamesessionstatemachine.cpp:1162-1220 |
START_LOADING_SAVE_GAME | – | → WAIT_FOR_SAVE_GAME(旧逻辑已移入存档队列) | 否 | src/dev_ng/game/Core/gamesessionstatemachine.cpp:1222-1227 |
WAIT_FOR_SAVE_GAME | – | 空等,存档队列完成后调用 HandleLoadedSaveGame() | 跨帧 | src/dev_ng/game/Core/gamesessionstatemachine.cpp:1229-1234 |
HANDLE_LOADED_SAVE_GAME | – | 重建会话并反序列化存档 | 是 | src/dev_ng/game/Core/gamesessionstatemachine.cpp:1236-1341 |
LOAD_LEVEL | – | InitLevel(必要时)+ InitSession | 是 | src/dev_ng/game/Core/gamesessionstatemachine.cpp:1474-1538 |
CHANGE_LEVEL | – | 淡出 → 关会话+关卡 → 开关卡+会话 | 淡出跨帧,加载阻塞 | src/dev_ng/game/Core/gamesessionstatemachine.cpp:1343-1361 |
SHUTDOWN_FOR_LANDING_PAGE | UI_LANDING_PAGE_ENABLED | 淡出并返回落地页 | 否 | src/dev_ng/game/Core/gamesessionstatemachine.cpp:1365-1376 |
ENTER_LANDING_PAGE | UI_LANDING_PAGE_ENABLED | Gen9 启动落地页 / PC 旧版落地页循环 | PC 版阻塞 | src/dev_ng/game/Core/gamesessionstatemachine.cpp:1378-1407 |
DISMISS_LANDING_PAGE | UI_LANDING_PAGE_ENABLED | 关闭落地页,必要时开始加载 | 否 | src/dev_ng/game/Core/gamesessionstatemachine.cpp:1425-1470 |
枚举定义:src/dev_ng/game/Core/gamesessionstatemachine.h:21-56;UI_LANDING_PAGE_ENABLED 为 RSG_PC || IS_GEN9_PLATFORM || GEN9_UI_SIMULATION_ENABLED src/dev_ng/game/frontend/landing_page/LandingPageConfig.h:15。上表中 cpp: 均指 src/dev_ng/game/Core/gamesessionstatemachine.cpp。
3. 状态图#
图中证据(cpp = gamesessionstatemachine.cpp):
- 初始状态:
src/dev_ng/game/Core/gamesessionstatemachine.cpp:117-133;2FA 之后:src/dev_ng/game/Core/gamesessionstatemachine.cpp:1114-1118;INIT:src/dev_ng/game/Core/gamesessionstatemachine.cpp:688-691 CHECK_FOR_RGSC三个出口:src/dev_ng/game/Core/gamesessionstatemachine.cpp:245-269;Steam 出口:src/dev_ng/game/Core/gamesessionstatemachine.cpp:439-443;RGSC 失败的取消分支:src/dev_ng/game/Core/gamesessionstatemachine.cpp:366-379;权益失败/结束:src/dev_ng/game/Core/gamesessionstatemachine.cpp:470-480- 登录检查出口:
src/dev_ng/game/Core/gamesessionstatemachine.cpp:724-783;档案→语言/伽马:src/dev_ng/game/Core/gamesessionstatemachine.cpp:791-795;语言→伽马:src/dev_ng/game/Core/gamesessionstatemachine.cpp:845;伽马→邀请:src/dev_ng/game/Core/gamesessionstatemachine.cpp:868 - 邀请三出口:
src/dev_ng/game/Core/gamesessionstatemachine.cpp:884-911;自动读档决策:src/dev_ng/game/Core/gamesessionstatemachine.cpp:695-719、src/dev_ng/game/Core/gamesessionstatemachine.cpp:947-958;AUTOLOAD_SAVE_GAME结束:src/dev_ng/game/Core/gamesessionstatemachine.cpp:1059 LOAD_LEVEL:src/dev_ng/game/Core/gamesessionstatemachine.cpp:1480-1537;落地页三状态:src/dev_ng/game/Core/gamesessionstatemachine.cpp:1365-1470、src/dev_ng/game/Core/gamesessionstatemachine.cpp:486-496;请求方:src/dev_ng/game/frontend/landing_page/LandingPage.cpp:56-65- 运行期 API:
src/dev_ng/game/Core/gamesessionstatemachine.cpp:510-559;REINIT:src/dev_ng/game/Core/gamesessionstatemachine.cpp:135-164、src/dev_ng/game/Core/gamesessionstatemachine.cpp:239 - 新游戏/读档/换关完成:
src/dev_ng/game/Core/gamesessionstatemachine.cpp:1217、src/dev_ng/game/Core/gamesessionstatemachine.cpp:1226、src/dev_ng/game/Core/gamesessionstatemachine.cpp:533-534、src/dev_ng/game/Core/gamesessionstatemachine.cpp:1339、src/dev_ng/game/Core/gamesessionstatemachine.cpp:1359
图中省略了次要边:PC 上 START_AUTOLOAD 在 RGSC 初始化失败过的情况下转入 HANDLE_RGSC_FAILED src/dev_ng/game/Core/gamesessionstatemachine.cpp:709-714;CHECK_SIGN_IN_STATE 在已登录时也可能转入 STEAM_VERIFICATION 或 REQUIRE_PC_ENTITLEMENT(编译条件下)src/dev_ng/game/Core/gamesessionstatemachine.cpp:759-772;Gen9 下 START_NEW_GAME、HANDLE_LOADED_SAVE_GAME、反序列化失败都可能转入 ENTER_LANDING_PAGE。
4. 首次启动的引导流程#
按平台,RunGame_OnEnter 中那一次 Update() 依次经过的状态(无邀请、正常登录、已安装):
| 平台 | 状态序列 |
|---|---|
| PC(无 Steam) | CHECK_FOR_RGSC → REQUIRE_PC_ENTITLEMENT → LOAD_PROFILE → CHOOSE_LANGUAGE → GAMMA_CALIBRATION → CHECK_FOR_INVITE → (ENTER_LANDING_PAGE →) START_AUTOLOAD → ENUMERATE_SAVED_GAMES → AUTOLOAD_SAVE_GAME 或 LOAD_LEVEL → IDLE |
| PS4 / Xbox One | INIT → CHECK_SIGN_IN_STATE → LOAD_PROFILE → CHOOSE_LANGUAGE → GAMMA_CALIBRATION → CHECK_FOR_INVITE → START_AUTOLOAD → ENUMERATE_SAVED_GAMES → AUTOLOAD_SAVE_GAME 或 LOAD_LEVEL → IDLE |
两点由代码直接推出:
- PC 不经过
CHECK_SIGN_IN_STATE:只有INIT会进入它,而 PC 的Init()直接进入CHECK_FOR_RGSC,后者的正常出口是权益检查而非INITsrc/dev_ng/game/Core/gamesessionstatemachine.cpp:126-131、src/dev_ng/game/Core/gamesessionstatemachine.cpp:254-269。 - 主机没有权益/Steam 状态:
RSG_ENTITLEMENT_ENABLED在非 PC 上恒为 0,在 PC 上为!PRELOADED_SOCIAL_CLUB(32 位构建为 1,即关闭)src/dev_ng/rage/base/src/rline/entitlement/rlrosentitlement.h:16-34;__STEAM_BUILD默认关闭src/dev_ng/rage/base/src/file/file_config.h:22-25;YubiKey 宏RAGE_SEC_ENABLE_YUBIKEY定义为0 && RSG_PCsrc/dev_ng/rage/base/src/security/ragesecengine.h:47。
开发参数可短路引导:-noSocialClub(PC 非 final 或 MASTER_NO_SCUI)直接 LOAD_LEVEL;-noprofilesignin、-unattended 让登录检查直接 LOAD_LEVEL;-noautoload 禁止自动读档(除非命令行/落地页要求“直接进多人”);-skipLandingPage 跳过落地页;-stopLoadingScreensEarly 让代码而非脚本关闭加载画面 src/dev_ng/game/Core/gamesessionstatemachine.cpp:58-79、src/dev_ng/game/Core/gamesessionstatemachine.cpp:245-252、src/dev_ng/game/Core/gamesessionstatemachine.cpp:724-738、src/dev_ng/game/Core/gamesessionstatemachine.cpp:695-698。
5. PC 专属状态#
5.1 Social Club 失败处理#
HANDLE_RGSC_FAILED 是一个 while(true) 循环:每 33 ms 调 CApp::CheckExit(),根据 g_rlPc.GetInitErrorCode() 选择文本标签(无 DLL、版本过旧、未经启动器启动、通用错误;非 final 下还附带错误码枚举名),显示 OK/Cancel 警告屏 src/dev_ng/game/Core/gamesessionstatemachine.cpp:272-353。
- OK:用
ShellExecuteA打开 Rockstar 支持页面并请求退出到 Windows(仅一次)。 - Cancel:final 构建请求退出;非 final 构建允许开发者继续,清除失败标志、关闭对话屏并
SetState(LOAD_PROFILE, true)src/dev_ng/game/Core/gamesessionstatemachine.cpp:355-379。 - 每次循环调用
UpdateInitialDialogueScreensOnly()与gRenderThreadInterface.FlushAndDeviceCheck()src/dev_ng/game/Core/gamesessionstatemachine.cpp:381-382。
5.2 权益检查与离开守卫#
REQUIRE_PC_ENTITLEMENT 在 RSG_ENTITLEMENT_ENABLED 时 CEntitlementManager::StartCheck(),然后在 IsPending() 期间循环:CheckExit → CEntitlementManager::Update()(注释要求它在泵函数之前,否则会清掉 CheckExit 设置的警告屏标志)→ 泵函数 → D3D11 刷新;若 SCUI 处于失败态(网站加载失败),设置错误码并转 HANDLE_RGSC_FAILED src/dev_ng/game/Core/gamesessionstatemachine.cpp:447-481。IsCheckingEntitlement() 在 REINIT/REQUIRE_PC_ENTITLEMENT/HANDLE_RGSC_FAILED/STEAM_VERIFICATION 时为真,IsEntitlementUpdating() 反映是否处于自旋检查中 src/dev_ng/game/Core/gamesessionstatemachine.h:70-73。
离开守卫:PC 上若当前状态是 REQUIRE_PC_ENTITLEMENT 或 REINIT,SetState(newState) 只有在第二个参数 bCanLeaveEntitlementState == true 时才生效,否则记录日志并忽略 src/dev_ng/game/Core/gamesessionstatemachine.cpp:1683-1736。因此外部代码(如落地页、脚本触发的 StartNewGame)无法在权益检查中途把状态机“拉走”;只有这两个状态自身的出口传入 true。
5.3 ReInit / REINIT:登录身份变化#
CLiveManager::OnActionProfileChange() 调 CGame::HandleSignInStateChange() src/dev_ng/game/network/Live/livemanager.cpp:1634-1641,后者按平台分派:Gen9 → ReturnToLandingPage(Reason_SignInStateChange)(非 final 的 -netSignOutStartNewGame 改为新游戏);PC → ReInitStateMachine(当前关卡);其他 → StartNewGame(当前关卡) src/dev_ng/game/Core/game.cpp:1638-1660。
ReInit(level)(RSG_ENTITLEMENT_ENABLED):复位权益管理器;若当前在落地页或伽马校准 → REQUIRE_PC_ENTITLEMENT;若不在任一权益状态 → REINIT 并记录关卡;若已在权益状态 → 重新开始检查。若基准测试脚本是从落地页启动的,则请求重启游戏(bugstar 2307904 的修复)。未启用权益系统时退化为 StartNewGame(level) src/dev_ng/game/Core/gamesessionstatemachine.cpp:135-164。
ProcessReInitState:显示加载画面 → 若会话已初始化则 ShutdownSession → 关卡不同则 ShutdownLevel + InitLevel,或关卡未初始化且为默认关卡则 InitLevel → InitSession(level, false) → 权益自旋循环(期间手动泵 fwTimer::Update、控制线程、网络、警告屏、鼠标、Scaleform 帧末更新、渲染线程刷新,并在 g_GameRunning 时暂停/恢复外层 “Update” 计时条)→ 停加载画面 → SetState(IDLE, true) src/dev_ng/game/Core/gamesessionstatemachine.cpp:167-240。
5.4 双因素认证与 Steam#
两者都是条件编译的阻塞循环,默认构建中关闭(见第 4 节)。TWO_FACTOR_AUTH_CHECK 在法律画面结束后根据 YubiKey 插入/验证状态显示提示,直到“已验证且已拔出” src/dev_ng/game/Core/gamesessionstatemachine.cpp:1062-1112。STEAM_VERIFICATION 轮询 CSteamVerification,失败时显示错误并在确认后退出 src/dev_ng/game/Core/gamesessionstatemachine.cpp:386-438。本页不展开这些验证的判定细节。
6. 自动读档#
START_AUTOLOAD:游戏未安装(CFileMgr::IsGameInstalled()为假)或-noautoload→ 直接LOAD_LEVEL(关卡取CGameLogic::GetRequestedLevelIndex());否则m_StartedAutoload = true、CSavegameAutoload::InitialiseAutoLoadAtStartOfGame(),进入ENUMERATE_SAVED_GAMESsrc/dev_ng/game/Core/gamesessionstatemachine.cpp:693-720。ENUMERATE_SAVED_GAMES:(除非启用了“快速扫描 + 另一线程慢扫描”)阻塞枚举存档,再阻塞询问“读档还是新游戏”;新游戏 →LOAD_LEVEL(关卡取存档的 episode 号),读档 →AUTOLOAD_SAVE_GAMEsrc/dev_ng/game/Core/gamesessionstatemachine.cpp:914-959。AUTOLOAD_SAVE_GAME:BeginGameAutoload()后阻塞CSavegameLoad::GenericLoad();成功则内联调用ProcessLoadLevelState(true)(加载关卡与会话,bLoadingSavegame=true使InitSession跳过新游戏专属步骤)→CNetwork::Update(true)→CSavegameLoad::DeSerialize();反序列化成功调用InitLevelAfterLoadingFromMemoryCard与DoGameSpecificStuffAfterSuccessLoad(true),失败则(PC 先显示“最近的存档已损坏”)关闭会话并以新游戏方式重建会话(Gen9 改为回落地页);读取失败则ProcessLoadLevelState(false)当作新游戏src/dev_ng/game/Core/gamesessionstatemachine.cpp:961-1060。- 回到
IDLE后的第一次ProcessIdleState调用CSavegameAutoload::ResetAfterAutoloadAtStartOfGame()src/dev_ng/game/Core/gamesessionstatemachine.cpp:678-686。
边界:ProcessLoadLevelState 自己会 SetState(IDLE),但 ProcessAutoLoadSaveGameState 在末尾再 SetState(newState) 覆盖它(newState 默认 IDLE,Gen9 反序列化失败时为 ENTER_LANDING_PAGE)src/dev_ng/game/Core/gamesessionstatemachine.cpp:967、src/dev_ng/game/Core/gamesessionstatemachine.cpp:1536、src/dev_ng/game/Core/gamesessionstatemachine.cpp:1059。
7. 语言选择与伽马校准#
CHOOSE_LANGUAGE:先显示语言选择屏(CLanguageSelect::ShowLanguageSelectScreen),取系统语言(未定义则英语)与档案中的DISPLAY_LANGUAGE。若两者对应不同的语言文件,则关闭游戏流与加载画面、切到默认渲染函数并Flush(true)、杀掉所有 Scaleform 影片并清空 Scaleform 存储、CText::Shutdown/Init并重新加载文本,然后重建加载画面(LOADINGSCREEN_CONTEXT_LOADLEVEL)与游戏流;若只是语言不同而文件相同,则TheText.ReloadAfterLanguageChange()src/dev_ng/game/Core/gamesessionstatemachine.cpp:798-846。GAMMA_CALIBRATION:若CDisplayCalibration::ShouldActivateAtStartup(),等待法律画面结束、挂起加载画面、加载并运行校准影片、把新伽马写入档案、移除影片并恢复加载画面src/dev_ng/game/Core/gamesessionstatemachine.cpp:848-869。- PS3 走不同顺序:语言选择在
CGame::PreLoadingScreensInit中已完成,LOAD_PROFILE直接进入伽马校准src/dev_ng/game/Core/game.cpp:1073-1076、src/dev_ng/game/Core/gamesessionstatemachine.cpp:791-795。
8. 运行期转换:新游戏、读档、换关#
8.1 触发者#
| API | 前置条件 | 典型调用者 | 证据 |
|---|---|---|---|
StartNewGame(level) | 断言 IDLE;会话未初始化时直接返回 | 暂停菜单“新游戏”、脚本 SHUTDOWN_AND_LAUNCH_SINGLE_PLAYER_GAME/NETWORK_GAME、回放退出、存档队列、非 PC 的登录变化 | src/dev_ng/game/Core/gamesessionstatemachine.cpp:510-521、src/dev_ng/game/frontend/PauseMenu.cpp:3584-3592、src/dev_ng/game/script/commands_network.cpp:7604-7616 |
LoadSaveGame() | 断言 IDLE | 暂停菜单读档 | src/dev_ng/game/Core/gamesessionstatemachine.cpp:523-527、src/dev_ng/game/frontend/PauseMenu.cpp:3690-3701 |
HandleLoadedSaveGame() | 断言 WAIT_FOR_SAVE_GAME | 存档队列 CSavegameQueuedOperation_ManualLoad 在 GenericLoad() 完成后调用 | src/dev_ng/game/Core/gamesessionstatemachine.cpp:529-535、src/dev_ng/game/SaveLoad/savegame_queued_operations.cpp:2644-2656 |
ChangeLevel(level) | 断言 IDLE | 脚本 SWITCH_TO_LEVEL(关卡索引 1–5) | src/dev_ng/game/Core/gamesessionstatemachine.cpp:552-559、src/dev_ng/game/script/commands_misc.cpp:4008-4014 |
SetForceInitLevel() | – | 冒烟测试;使下一次新游戏强制重载关卡 | src/dev_ng/game/Core/gamesessionstatemachine.h:123、src/dev_ng/game/tools/SmokeTest.cpp:515 |
值得注意:单人与多人之间的切换(SHUTDOWN_AND_LAUNCH_NETWORK_GAME)也走 StartNewGame——即关闭并重建整个游戏会话(SHUTDOWN_SESSION + INIT_SESSION);同关卡时不重载地图。
8.2 重启淡出 ProcessRestartFade#
START_NEW_GAME 与 CHANGE_LEVEL 每帧调用它直到返回 true src/dev_ng/game/Core/gamesessionstatemachine.cpp:1540-1573:
- 有警告屏 → 等玩家接受后移除;
- 画面未淡出且未在淡出 →
camInterface::FadeOut(300); - 淡出未到 1.0 → 等待;
- 暂停菜单仍打开 → 关闭它(注释:给加载渲染函数留一帧,修复 bug 176273);
- 否则停手柄震动,返回 true。
这是状态机中唯一真正跨帧推进的部分,期间 ShouldDoFullUpdateRender() 为 false,游戏运行 SIMPLE_MAIN_UPDATE。
8.3 START_NEW_GAME / HANDLE_LOADED_SAVE_GAME / CHANGE_LEVEL#
| 状态 | 关键步骤 | 证据 |
|---|---|---|
START_NEW_GAME | (Gen9 先检查故事模式权益,否则转落地页)淡出完成后:结束启动条 → 显示加载画面 → ShutdownSession → 若 m_ForceInitLevel 或关卡变化则 ShutdownLevel → Bank 的 -askBeforeRestart 弹框(允许替换镜像文件)→ 必要时 InitLevel → InitSession(level, false) → 停加载画面 → IDLE。每帧还向 SuperConductor 发送 MuteGameWorld 消息 | src/dev_ng/game/Core/gamesessionstatemachine.cpp:1162-1220 |
HANDLE_LOADED_SAVE_GAME | (Gen9 权益检查)显示加载画面 → 关卡号取 CSavegameList → ShutdownSession → 关卡变化则 ShutdownLevel/InitLevel → InitSession(level, true) → 停加载画面 → DeSerialize();失败时 PC 弹“无法加载损坏存档”并以新游戏方式重建,成功则 InitLevelAfterLoadingFromMemoryCard、DoGameSpecificStuffAfterSuccessLoad(true)、按新档案刷新暂停菜单选项 | src/dev_ng/game/Core/gamesessionstatemachine.cpp:1236-1341 |
CHANGE_LEVEL | 淡出完成后:显示加载画面 → ShutdownSession → ShutdownLevel → InitLevel → InitSession → 停加载画面 → IDLE(无条件重载关卡) | src/dev_ng/game/Core/gamesessionstatemachine.cpp:1343-1361 |
LOAD_LEVEL | (Gen9 权益检查,必要时清除脚本路由链接以免重定向循环)显示加载画面 → Gen9 下仅在关卡未初始化时 InitLevel → InitSession(level, bLoadingSavegame) → 再次 CModelIndex::MatchAllModelStrings()(DLC 可能新增模型)→ 单人游戏且无待处理脚本路由链接时停加载画面 → IDLE | src/dev_ng/game/Core/gamesessionstatemachine.cpp:1474-1538 |
其他系统通过查询当前状态区分“新游戏”与“读档”:例如统计系统在 INIT_SESSION 中判断 START_NEW_GAME / HANDLE_LOADED_SAVE_GAME / AUTOLOAD_SAVE_GAME src/dev_ng/game/Stats/StatsDataMgr.cpp:1757-1787,人口流式在 START_NEW_GAME 时有特殊处理 src/dev_ng/game/streaming/populationstreaming.cpp:703。这是隐式耦合:改变状态顺序会影响这些系统。
9. 落地页#
| 方面 | PC 旧版落地页(CLegacyLandingScreen) | Gen9 落地页(CLandingPage) |
|---|---|---|
| 启用条件 | UI_LANDING_PAGE_ENABLED(PC 恒真)且 CLegacyLandingScreen::IsLandingPageEnabled();-benchmark、-skipLandingPage 跳过 | GEN9_LANDING_PAGE_ENABLED(需 RSG_GEN9)且 CLandingPage::ShouldShowOnBoot() |
| 进入 | 引导流程中 CHECK_FOR_INVITE → ENTER_LANDING_PAGE | 同左,或运行期 CLandingPage::SetShouldLaunch → SHUTDOWN_FOR_LANDING_PAGE(淡出、加载画面切到 LANDING 上下文)→ ReturnToLandingPage(清脚本路由链接、ShutdownSession、重置存储)→ ENTER_LANDING_PAGE |
| 运行方式 | 在 ENTER_LANDING_PAGE 内 while(!SLegacyLandingScreen::GetInstance().Update()); 阻塞,结束后 START_AUTOLOAD | CLandingPage::Launch() 后立即 IDLE;之后每帧由 CGame::Update 运行 LANDING_PAGE_UPDATE |
| 退出 | 循环结束 | CLandingPage::SetShouldDismiss → DISMISS_LANDING_PAGE:无会话时按脚本路由链接决定单人/多人(默认 Freeroam,Editor/Creator 视为非多人),设置“直接进多人”标志与加载画面顺序后 START_AUTOLOAD;有会话则 IDLE |
| 证据 | src/dev_ng/game/frontend/landing_page/LandingPageArbiter.cpp:42-63、src/dev_ng/game/Core/gamesessionstatemachine.cpp:1394-1406 | src/dev_ng/game/Core/gamesessionstatemachine.cpp:1365-1393、src/dev_ng/game/Core/gamesessionstatemachine.cpp:1409-1470、src/dev_ng/game/Core/gamesessionstatemachine.cpp:486-496 |
边界:在非 Gen9 构建中 ReturnToLandingPage() 的函数体为空,因此若有人在 PC 上设置 SHUTDOWN_FOR_LANDING_PAGE,状态将停留在该状态、每帧淡出并尝试初始化加载画面,不会自行离开。这表明运行期“返回落地页”只为 Gen9 设计(推断;没有找到 PC 上设置该状态的调用者,CLandingPage 只在 Gen9 编译)。
Gen9 故事模式权益:UserAllowedToEnterStoryMode() = UserHasStoryModeEntitlement() || IgnoreStoryModeEntitlement();后者在待处理的脚本路由链接目标不是故事模式时返回 true(某些非故事模式需要途经单人会话,如编辑器);无权益时 ShowStoryModeUpsell() 暂时直接回落地页(TODO 注释)src/dev_ng/game/Core/gamesessionstatemachine.cpp:1738-1777。
10. 阻塞循环中的“泵”与加载画面#
阻塞状态在循环中不会运行常规主循环,因此需要手动推进最少量的系统:
| 函数 | 推进的系统 | 证据 |
|---|---|---|
UpdateInitialDialogueScreensOnly | CSystem::BeginUpdate(含 fwTimer::Update)→ 控制线程启动/等待 → PC 鼠标与流式加载器(发出请求、处理异步文件)→ 警告屏 → 档案设置 → 存档对话框出现时挂起加载画面、关闭时恢复 → CNetwork::Update(true) → CSystem::EndUpdate → 初始化计时条帧 | src/dev_ng/game/Core/gamesessionstatemachine.cpp:1594-1626 |
ShutdownInitialDialogueScreensOnly | 关闭视口会话、最后一次警告屏更新、完全淡出、让主正交视口下次初始化会话时重新淡入、恢复加载画面 | src/dev_ng/game/Core/gamesessionstatemachine.cpp:1628-1642 |
UpdateLoadFailedDialogueScreens(PC) | 与 REINIT 循环相同的一组泵(含 CApp::CheckExit、Scaleform 帧末更新、FlushAndDeviceCheck) | src/dev_ng/game/Core/gamesessionstatemachine.cpp:1644-1680 |
StartDisplayingLoadingScreens / StopDisplayingLoadingScreens | 前者 CLoadingScreens::Init(LOADINGSCREEN_CONTEXT_LOADLEVEL) 并允许 Scaleform 在帧末更新;后者不关闭加载画面(final 构建“交给脚本关闭”,非 final 仅 -stopLoadingScreensEarly 时关闭),只关闭帧末 Scaleform 更新 | src/dev_ng/game/Core/gamesessionstatemachine.cpp:1575-1592 |
在这些阻塞期间,主线程不调用 gRenderThreadInterface.Synchronise();渲染线程运行的是加载画面渲染函数(CLoadingScreens::RenderLoadingScreens 或 CGtaOldLoadingScreen::LoadingRenderFunction),PC 上循环中的 FlushAndDeviceCheck() 负责排空 drawlist 与处理设备状态。详见 threading-and-render-thread.md。长时间 skeleton 初始化期间则依靠 KeepAliveCallback 维持网络与加载画面(见 game-skeleton-and-main-loop.md)。
11. 未决问题 / 证据不足#
- Gen9 代码不完整:
livemanager.cpp在RSG_GEN9下设置CGameSessionStateMachine::SHUTDOWN_FOR_IIS,但状态枚举中没有该值src/dev_ng/game/network/Live/livemanager.cpp:1602-1614、src/dev_ng/game/Core/gamesessionstatemachine.h:21-56。这表明本快照中的 Gen9(PS5/XSX)分支与当前头文件不一致,Gen9 落地页/初始交互屏流程无法完整验证。 TWO_FACTOR_AUTH_CHECK的枚举只在__WIN32PC下存在,但Init()在RAGE_SEC_ENABLE_YUBIKEY下无条件设置它;由于该宏为0 && RSG_PC,当前不会触发,但若在非 PC 上启用会无法编译(推断)。CSavegameAutoload、CSavegameLoad、存档队列的内部状态机属于存档页面,本页只描述它们与会话状态机的接口。- 状态转换完全由代码阅读得出,未运行验证;各对话框文本标签只列名称,未核对本地化内容。