diff --git a/packages/webgal/src/Core/controller/stage/resetStage.ts b/packages/webgal/src/Core/controller/stage/resetStage.ts index 26327d9bb..fc62f39d3 100644 --- a/packages/webgal/src/Core/controller/stage/resetStage.ts +++ b/packages/webgal/src/Core/controller/stage/resetStage.ts @@ -3,7 +3,12 @@ import { WebGAL } from '@/Core/WebGAL'; import { initState, stageStateManager } from '@/Core/Modules/stage/stageStateManager'; import { stopFast } from '@/Core/controller/gamePlay/fastSkip'; -export const resetStage = (resetBacklog: boolean, resetSceneAndVar = true) => { +export interface ResetStageOptions { + commitStageState?: boolean; +} + +export const resetStage = (resetBacklog: boolean, resetSceneAndVar = true, options: ResetStageOptions = {}) => { + const { commitStageState = true } = options; /** * 清空运行时 */ @@ -24,8 +29,16 @@ export const resetStage = (resetBacklog: boolean, resetSceneAndVar = true) => { // 清空舞台状态表 const initSceneDataCopy = cloneDeep(initState); const currentVars = stageStateManager.getCalculationStageState().GameVar; - stageStateManager.resetAllStageState(initSceneDataCopy, { skipAnimation: true }); + if (commitStageState) { + stageStateManager.resetAllStageState(initSceneDataCopy, { skipAnimation: true }); + } else { + stageStateManager.resetCalculationStageState(initSceneDataCopy); + } if (!resetSceneAndVar) { - stageStateManager.setStageAndCommit('GameVar', currentVars); + if (commitStageState) { + stageStateManager.setStageAndCommit('GameVar', currentVars); + } else { + stageStateManager.setStage('GameVar', currentVars); + } } }; diff --git a/packages/webgal/src/Core/util/syncWithEditor/previewSyncRuntime.ts b/packages/webgal/src/Core/util/syncWithEditor/previewSyncRuntime.ts index a76ae090e..560139202 100644 --- a/packages/webgal/src/Core/util/syncWithEditor/previewSyncRuntime.ts +++ b/packages/webgal/src/Core/util/syncWithEditor/previewSyncRuntime.ts @@ -106,6 +106,8 @@ export const startPreviewSyncRuntime = () => { const targetTransformBaselines = createTargetTransformBaselineManager(); const embeddedLaunchIdPromise = requestEmbeddedLaunchId(); let transport!: PreviewSyncTransport; + let nextSyncSceneRevision = 0; + let activeSyncSceneRevision: number | null = null; const createRequestId = () => `req-${Date.now()}-${Math.random().toString(16).slice(2)}`; @@ -133,9 +135,33 @@ export const startPreviewSyncRuntime = () => { lastPublishedSentenceId = null; lastPublishedStageState = null; setEffectBaselines.clear(); + activeSyncSceneRevision = null; + WebGAL.gameplay.isFastPreview = false; targetTransformBaselines.invalidateCurrentRevision(); }; + const startSyncSceneTransaction = () => { + nextSyncSceneRevision += 1; + activeSyncSceneRevision = nextSyncSceneRevision; + return activeSyncSceneRevision; + }; + + const isActiveSyncSceneRevision = (revision: number) => activeSyncSceneRevision === revision; + + const finishSyncSceneTransaction = (revision: number) => { + if (!isActiveSyncSceneRevision(revision)) { + return false; + } + + activeSyncSceneRevision = null; + return true; + }; + + const cancelActiveSyncScene = () => { + activeSyncSceneRevision = null; + WebGAL.gameplay.isFastPreview = false; + }; + const buildStageStateSnapshot = (stageState: StageStateSnapshot): StageSnapshotUpdatedPayload['stageState'] => { return JSON.parse(JSON.stringify(stageState)) as StageSnapshotUpdatedPayload['stageState']; }; @@ -152,6 +178,9 @@ export const startPreviewSyncRuntime = () => { if (!registered) { return; } + if (activeSyncSceneRevision !== null) { + return; + } const sceneName = WebGAL.sceneManager.sceneData.currentScene.sceneName; const sentenceId = WebGAL.sceneManager.sceneData.currentSentenceId; @@ -178,6 +207,10 @@ export const startPreviewSyncRuntime = () => { } }; + const publishSettledStageSnapshot = () => { + publishStageSnapshot(true, stageStateManager.getViewStageState()); + }; + const registerPreview = async (socket: PreviewSyncTransportSocket) => { const requestId = createRequestId(); pendingRegisterRequestId = requestId; @@ -222,6 +255,8 @@ export const startPreviewSyncRuntime = () => { }; const handleSyncScene = (payload: SyncScenePayload) => { + const syncSceneRevision = startSyncSceneTransaction(); + const isLatestSyncScene = () => isActiveSyncSceneRevision(syncSceneRevision); setEffectBaselines.clear(); const { transformBaselineRevision } = payload; if (transformBaselineRevision) { @@ -232,7 +267,11 @@ export const startPreviewSyncRuntime = () => { executePreviewSyncSceneCommand(payload, { onFastPreviewTimeout: emitFastPreviewTimeout, + isLatest: isLatestSyncScene, onBeforeTargetScriptExecute: () => { + if (!isLatestSyncScene()) { + return; + } if (!transformBaselineRevision) { return; } @@ -243,22 +282,37 @@ export const startPreviewSyncRuntime = () => { ); }, onSettled: (result) => { + if (!finishSyncSceneTransaction(syncSceneRevision)) { + return; + } + + if (result === null) { + if (transformBaselineRevision) { + targetTransformBaselines.failRevision(transformBaselineRevision); + } + return; + } + if (!transformBaselineRevision) { + publishSettledStageSnapshot(); return; } const isSyncSettled = isTargetTransformBaselineSyncSettled(result, payload); if (!isSyncSettled || !targetTransformBaselines.publishCapturedSnapshot(transformBaselineRevision)) { targetTransformBaselines.failRevision(transformBaselineRevision); + publishSettledStageSnapshot(); return; } setEffectBaselines.clear(); + publishSettledStageSnapshot(); }, }); }; const handleRunSnippet = (payload: RunSnippetPayload) => { + cancelActiveSyncScene(); setEffectBaselines.clear(); targetTransformBaselines.invalidateCurrentRevision(); applyPreviewDebugVariables(payload.debugVariables); @@ -293,6 +347,7 @@ export const startPreviewSyncRuntime = () => { }; const handleRunSceneContent = (payload: RunSceneContentPayload) => { + cancelActiveSyncScene(); setEffectBaselines.clear(); targetTransformBaselines.invalidateCurrentRevision(); resetStage(true); diff --git a/packages/webgal/src/Core/util/syncWithEditor/runtime/previewSyncSceneCommand.ts b/packages/webgal/src/Core/util/syncWithEditor/runtime/previewSyncSceneCommand.ts index 407273fb9..46dc17248 100644 --- a/packages/webgal/src/Core/util/syncWithEditor/runtime/previewSyncSceneCommand.ts +++ b/packages/webgal/src/Core/util/syncWithEditor/runtime/previewSyncSceneCommand.ts @@ -29,16 +29,19 @@ export interface PreviewSyncSceneCommandCallbacks { onFastPreviewTimeout?: FastPreviewTimeoutEmitter; onBeforeTargetScriptExecute?: () => void; onSettled?: (result: FastPreviewResult | null) => void; + isLatest?: () => boolean; } interface RunFastPreviewOptions { onBeforeTargetScriptExecute?: () => void; + isLatest?: () => boolean; } export function executePreviewSyncSceneCommand( { sceneName, sentenceId, debugVariables, settleMode = 'normal' }: SyncScenePayload, callbacks: PreviewSyncSceneCommandCallbacks = {}, ): void { + const isLatest = () => callbacks.isLatest?.() ?? true; logger.warn('正在跳转到' + sceneName + ':' + sentenceId); WebGAL.gameplay.isFastPreview = false; @@ -57,22 +60,39 @@ export function executePreviewSyncSceneCommand( sceneFetcher(sceneUrl) .then((rawScene) => { - resetStage(true); + if (!isLatest()) { + return; + } + + resetStage(true, true, { commitStageState: false }); applyPreviewDebugVariables(debugVariables); WebGAL.sceneManager.sceneData.currentScene = sceneParser(rawScene, sceneName, sceneUrl); const currentSceneName = WebGAL.sceneManager.sceneData.currentScene.sceneName; void runFastPreview(sentenceId, currentSceneName, callbacks.onFastPreviewTimeout, settleMode, { onBeforeTargetScriptExecute: callbacks.onBeforeTargetScriptExecute, + isLatest: callbacks.isLatest, }) .then((result) => { + if (!isLatest()) { + return; + } + callbacks.onSettled?.(result); }) .catch((error) => { + if (!isLatest()) { + return; + } + logger.error('实时预览跳转错误', error); callbacks.onSettled?.(null); }); }) .catch((error) => { + if (!isLatest()) { + return; + } + stopFast(); WebGAL.gameplay.isFastPreview = false; logger.error('实时预览跳转错误', error); @@ -86,7 +106,8 @@ export async function runFastPreview( onFastPreviewTimeout?: FastPreviewTimeoutEmitter, settleMode: SyncSceneSettleMode = 'normal', options: RunFastPreviewOptions = {}, -): Promise { +): Promise { + const isLatest = () => options.isLatest?.() ?? true; const fastPreviewStartTime = performance.now(); const baseSceneStackDepth = WebGAL.sceneManager.sceneData.sceneStack.length; stopFast(); @@ -117,6 +138,10 @@ export async function runFastPreview( try { while (shouldContinueFastPreview(sentenceId, currentSceneName, baseSceneStackDepth)) { + if (!isLatest()) { + return null; + } + const prevSentenceId = WebGAL.sceneManager.sceneData.currentSentenceId; const prevSceneName = WebGAL.sceneManager.sceneData.currentScene.sceneName; const isForwarded = forward({ @@ -135,6 +160,9 @@ export async function runFastPreview( if (awaitedSceneWrite) { suspendedElapsedMs += performance.now() - sceneWriteWaitStart; } + if (!isLatest()) { + return null; + } if (!isForwarded && !awaitedSceneWrite) { stopReason = 'no-progress'; @@ -164,7 +192,9 @@ export async function runFastPreview( } } } finally { - WebGAL.gameplay.isFastPreview = false; + if (isLatest()) { + WebGAL.gameplay.isFastPreview = false; + } } if (settleMode === 'immediate') { @@ -172,6 +202,10 @@ export async function runFastPreview( WebGAL.gameplay.performController.clearNonHoldPerformsFromStageState(); } + if (!isLatest()) { + return null; + } + commitForward(); const forwardedLineCount =