1
This commit is contained in:
@@ -2,7 +2,10 @@
|
||||
|
||||
import type { HydratedSavedGameSnapshot } from '../../persistence/runtimeSnapshotTypes';
|
||||
import { isAbortError } from '../../services/apiClient';
|
||||
import { rpgSnapshotClient } from '../../services/rpg-runtime';
|
||||
import {
|
||||
getRpgRuntimeSessionId,
|
||||
rpgSnapshotClient,
|
||||
} from '../../services/rpg-runtime';
|
||||
import type { GameState, StoryMoment } from '../../types';
|
||||
import { resumeServerRuntimeStory } from '../rpg-runtime-story/runtimeStoryCoordinator';
|
||||
import type { BottomTab } from './rpgSessionTypes';
|
||||
@@ -10,6 +13,8 @@ import type { BottomTab } from './rpgSessionTypes';
|
||||
const AUTO_SAVE_DELAY_MS = 400;
|
||||
|
||||
function canPersistSnapshot(gameState: GameState, story: StoryMoment | null) {
|
||||
// 中文注释:preview / test 模式、非 Story 场景、未选角、以及流式输出中的故事都不应入正式存档,
|
||||
// 否则容易把临时态或半成品叙事写进继续游戏链路。
|
||||
return (
|
||||
gameState.runtimePersistenceDisabled !== true &&
|
||||
gameState.runtimeMode !== 'preview' &&
|
||||
@@ -30,6 +35,8 @@ function normalizeBottomTab(bottomTab: string | null | undefined): BottomTab {
|
||||
}
|
||||
|
||||
function resolveRemoteSnapshotState(snapshot: HydratedSavedGameSnapshot) {
|
||||
// 中文注释:远端快照允许缺少局部 UI 状态;
|
||||
// 这里统一补底部 tab 的兜底值,避免恢复后落到非法面板名。
|
||||
return {
|
||||
gameState: snapshot.gameState,
|
||||
currentStory: snapshot.currentStory ?? null,
|
||||
@@ -75,6 +82,8 @@ export function useRpgSessionPersistence({
|
||||
const saveRequestIdRef = useRef(0);
|
||||
|
||||
const abortActiveSave = useCallback(() => {
|
||||
// 中文注释:自动存档是“后写覆盖前写”的串行语义;
|
||||
// 新一次保存开始前,主动打断旧请求,避免旧快照回写覆盖最新状态。
|
||||
saveControllerRef.current?.abort();
|
||||
saveControllerRef.current = null;
|
||||
setIsPersistingSnapshot(false);
|
||||
@@ -83,9 +92,8 @@ export function useRpgSessionPersistence({
|
||||
const persistSnapshot = useCallback(
|
||||
async (params: {
|
||||
payload: {
|
||||
gameState: GameState;
|
||||
sessionId: string;
|
||||
bottomTab: BottomTab;
|
||||
currentStory: StoryMoment | null;
|
||||
};
|
||||
logLabel: string;
|
||||
}) => {
|
||||
@@ -103,11 +111,12 @@ export function useRpgSessionPersistence({
|
||||
setPersistenceError(null);
|
||||
|
||||
try {
|
||||
// 中文注释:这里不再上传整份本地快照;
|
||||
// 前端只告诉后端“当前 session 需要 checkpoint”,真实 GameState 由服务端快照表读取。
|
||||
const snapshot = await rpgSnapshotClient.putSnapshot(
|
||||
{
|
||||
gameState: params.payload.gameState,
|
||||
sessionId: params.payload.sessionId,
|
||||
bottomTab: params.payload.bottomTab,
|
||||
currentStory: params.payload.currentStory,
|
||||
},
|
||||
{ signal: controller.signal },
|
||||
);
|
||||
@@ -158,6 +167,8 @@ export function useRpgSessionPersistence({
|
||||
hydrateControllerRef.current = controller;
|
||||
setIsHydratingSnapshot(true);
|
||||
|
||||
// 中文注释:登录后第一时间探测一次远端快照,
|
||||
// 让入口页能够准确判断“继续游戏”按钮是否可见。
|
||||
void rpgSnapshotClient
|
||||
.getSnapshot({ signal: controller.signal })
|
||||
.then((snapshot) => {
|
||||
@@ -207,12 +218,13 @@ export function useRpgSessionPersistence({
|
||||
|
||||
if (!canPersist) return;
|
||||
|
||||
// 中文注释:自动存档做一个很短的去抖,
|
||||
// 避免同一轮状态连锁更新时重复打多次快照请求。
|
||||
const timeoutId = window.setTimeout(() => {
|
||||
void persistSnapshot({
|
||||
payload: {
|
||||
gameState,
|
||||
sessionId: getRpgRuntimeSessionId(gameState),
|
||||
bottomTab,
|
||||
currentStory,
|
||||
},
|
||||
logLabel: 'failed to autosave remote snapshot',
|
||||
});
|
||||
@@ -235,11 +247,12 @@ export function useRpgSessionPersistence({
|
||||
return false;
|
||||
}
|
||||
|
||||
// 中文注释:手动存档和自动存档走同一套底层 persist 逻辑,
|
||||
// 差别只在于调用方可显式覆盖本次 checkpoint 的 session 与 UI tab。
|
||||
const snapshot = await persistSnapshot({
|
||||
payload: {
|
||||
gameState: nextGameState,
|
||||
sessionId: getRpgRuntimeSessionId(nextGameState),
|
||||
bottomTab: nextBottomTab,
|
||||
currentStory: nextStory,
|
||||
},
|
||||
logLabel: 'failed to save remote snapshot',
|
||||
});
|
||||
@@ -300,6 +313,8 @@ export function useRpgSessionPersistence({
|
||||
resetStoryState();
|
||||
const fallbackHydration = resolveRemoteSnapshotState(snapshot);
|
||||
|
||||
// 中文注释:继续游戏不是简单把旧 currentStory 塞回去,
|
||||
// 还要向服务端刷新一遍 runtime story,拿到当前服务端判定的可选动作与视图模型。
|
||||
const resumedState = await resumeServerRuntimeStory(snapshot).catch(
|
||||
(error) => {
|
||||
if (!isAbortError(error)) {
|
||||
|
||||
Reference in New Issue
Block a user