From 9f3219710b906e273189750f5faa0fc779f6db7b Mon Sep 17 00:00:00 2001 From: Akirami <66513481+A-kirami@users.noreply.github.com> Date: Thu, 18 Jun 2026 00:30:40 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20=E7=A6=81=E6=AD=A2=E9=A2=84=E8=A7=88?= =?UTF-8?q?=E5=91=BD=E4=BB=A4=E8=A7=A6=E5=8F=91=E8=87=AA=E5=8A=A8=E5=BF=AB?= =?UTF-8?q?=E9=80=9F=E5=AD=98=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 拆分 stage commit 的 React 通知和自动快速存档副作用, 让预览同步命令显式关闭 autoFastSave。 同时将 commitOptions 沿脚本执行、perform 生命周期、 场景切换续接、选择枝和输入框回调传递,避免异步续接路径 回落到默认快速存档行为。 新增 StageStateManager commit 行为测试,覆盖显式关闭自动快存、 变量提交关闭自动快存,以及 notifyReact 与 autoFastSave 相互独立。 --- .../Core/Modules/perform/performController.ts | 59 +++++++++++++------ .../webgal/src/Core/Modules/readHistory.ts | 5 +- .../Core/Modules/stage/stageStateManager.ts | 28 +++++---- .../Core/controller/gamePlay/nextSentence.ts | 21 +++---- .../src/Core/controller/gamePlay/runScript.ts | 7 ++- .../controller/gamePlay/scriptExecutor.ts | 13 ++-- .../src/Core/controller/scene/callScene.ts | 5 +- .../src/Core/controller/scene/changeScene.ts | 5 +- .../src/Core/controller/scene/restoreScene.ts | 5 +- .../src/Core/controller/stage/resetStage.ts | 7 ++- .../src/Core/gameScripts/callSceneScript.ts | 5 +- .../src/Core/gameScripts/changeSceneScript.ts | 5 +- .../src/Core/gameScripts/choose/index.tsx | 17 +++--- .../Core/gameScripts/getUserInput/index.tsx | 16 +++-- .../webgal/src/Core/gameScripts/jumpLabel.ts | 5 +- .../webgal/src/Core/gameScripts/label/jmp.ts | 5 +- packages/webgal/src/Core/initializeScript.ts | 2 +- packages/webgal/src/Core/parser/utils.ts | 3 +- .../util/syncWithEditor/previewSyncRuntime.ts | 19 +++--- .../runtime/previewSyncSceneCommand.ts | 6 +- 20 files changed, 144 insertions(+), 94 deletions(-) diff --git a/packages/webgal/src/Core/Modules/perform/performController.ts b/packages/webgal/src/Core/Modules/perform/performController.ts index 7d51c3c71..88a75c09f 100644 --- a/packages/webgal/src/Core/Modules/perform/performController.ts +++ b/packages/webgal/src/Core/Modules/perform/performController.ts @@ -3,6 +3,7 @@ import { ISentence } from '@/Core/controller/scene/sceneInterface'; import { nextSentence } from '@/Core/controller/gamePlay/nextSentence'; import { WEBGAL_NONE } from '@/Core/constants'; import { getBooleanArgByKey } from '@/Core/util/getSentenceArg'; +import type { IStageCommitOptions } from '@/Core/Modules/stage/stageStateManager'; import { stageStateManager } from '@/Core/Modules/stage/stageStateManager'; /** @@ -15,7 +16,7 @@ export const getRandomPerformName = (): string => { interface IPendingPerform { perform: IPerform; script: ISentence; - syncPerformState: boolean; + commitOptions: IStageCommitOptions; } export class PerformController { @@ -23,6 +24,7 @@ export class PerformController { private pendingPerformList: Array = []; private isCollectingPerforms = false; private stopTimeoutMap = new WeakMap>(); + private performCommitOptions = new WeakMap(); /** * 判断 perform 名称是否匹配(支持前缀匹配,用于清理并行演出) @@ -40,7 +42,12 @@ export class PerformController { this.isCollectingPerforms = false; } - public arrangeNewPerform(perform: IPerform, script: ISentence, syncPerformState = true) { + public arrangeNewPerform( + perform: IPerform, + script: ISentence, + syncPerformState = true, + commitOptions: IStageCommitOptions = {}, + ) { // 检查演出列表内是否有相同的演出,如果有,一定是出了什么问题 // 并行演出的 performName 带有唯一后缀,因此不会命中去重 const dupPerformIndex = this.performList.findIndex((p) => p.performName === perform.performName); @@ -51,6 +58,7 @@ export class PerformController { if (e.performName === perform.performName) { this.stopStartedPerform(e); this.clearPerformTimeout(e); + this.performCommitOptions.delete(e); this.performList.splice(i, 1); i--; } @@ -71,16 +79,16 @@ export class PerformController { stageStateManager.addPerform(performToAdd); } else { stageStateManager.addPerform(performToAdd); - stageStateManager.commit({ applyPixiEffects: false }); + stageStateManager.commit({ ...commitOptions, applyPixiEffects: false }); } } if (this.isCollectingPerforms) { - this.pendingPerformList.push({ perform, script, syncPerformState }); + this.pendingPerformList.push({ perform, script, commitOptions }); return; } - this.startPerform(perform, script); + this.startPerform(perform, script, commitOptions); if (!this.isCollectingPerforms) { stageStateManager.applyCommittedPixiEffects(); } @@ -89,8 +97,8 @@ export class PerformController { public commitPendingPerforms() { const performsToStart = this.pendingPerformList; this.pendingPerformList = []; - performsToStart.forEach(({ perform, script }) => { - this.startPerform(perform, script); + performsToStart.forEach(({ perform, script, commitOptions }) => { + this.startPerform(perform, script, commitOptions); }); } @@ -118,7 +126,7 @@ export class PerformController { return this.performList.some((e) => !e.isHoldOn && !e.skipNextCollect); } - public settleNonHoldPerforms() { + public settleNonHoldPerforms(commitOptions: IStageCommitOptions = {}) { let isGoNext = false; for (let i = 0; i < this.performList.length; i++) { const e = this.performList[i]; @@ -129,15 +137,16 @@ export class PerformController { if (!e.skipNextCollect) { this.stopStartedPerform(e); this.clearPerformTimeout(e); + this.performCommitOptions.delete(e); this.performList.splice(i, 1); i--; this.erasePerformFromState(e.performName); } } } - stageStateManager.commit(); + stageStateManager.commit(commitOptions); if (isGoNext) { - nextSentence(); + nextSentence(commitOptions); } } @@ -145,8 +154,9 @@ export class PerformController { stageStateManager.clearUncommittedNonHoldPerforms(); } - private startPerform(perform: IPerform, script: ISentence) { + private startPerform(perform: IPerform, script: ISentence, commitOptions: IStageCommitOptions = {}) { perform.isStarted = true; + this.performCommitOptions.set(perform, commitOptions); perform.startFunction?.(); // 时间到后自动清理演出 @@ -184,6 +194,7 @@ export class PerformController { if (!e.isHoldOn && this.matchPerformName(e.performName, name)) { this.stopStartedPerform(e); this.clearPerformTimeout(e); + const commitOptions = this.takeCommitOptions(e); /** * 在演出列表里删除演出对象的操作必须在调用 goNextWhenOver 之前 * 因为 goNextWhenOver 会调用 nextSentence,而 nextSentence 会清除目前未结束的演出 @@ -195,7 +206,7 @@ export class PerformController { i--; if (e.goNextWhenOver) { // nextSentence(); - this.goNextWhenOver(); + this.goNextWhenOver(commitOptions); } this.erasePerformFromState(name); } @@ -206,6 +217,7 @@ export class PerformController { if (this.matchPerformName(e.performName, name)) { this.stopStartedPerform(e); this.clearPerformTimeout(e); + const commitOptions = this.takeCommitOptions(e); /** * 在演出列表里删除演出对象的操作必须在调用 goNextWhenOver 之前(同上) */ @@ -213,7 +225,7 @@ export class PerformController { i--; if (e.goNextWhenOver) { // nextSentence(); - this.goNextWhenOver(); + this.goNextWhenOver(commitOptions); } /** * 从状态表里清除演出 @@ -242,10 +254,11 @@ export class PerformController { if (e.performName.startsWith(prefix) && (force || !e.isHoldOn)) { this.stopStartedPerform(e); this.clearPerformTimeout(e); + const commitOptions = this.takeCommitOptions(e); this.performList.splice(i, 1); i--; if (e.goNextWhenOver) { - this.goNextWhenOver(); + this.goNextWhenOver(commitOptions); } this.erasePerformFromState(e.performName); } @@ -265,11 +278,12 @@ export class PerformController { * 此问题对所有 goNextWhenOver 属性为真的演出都有影响,但只有 2 个演出有此问题 */ this.performList.splice(idx, 1); + const commitOptions = this.takeCommitOptions(perform); this.erasePerformFromState(perform.performName); - stageStateManager.commit(); + stageStateManager.commit(commitOptions); if (perform.goNextWhenOver) { // nextSentence(); - this.goNextWhenOver(); + this.goNextWhenOver(commitOptions); } } @@ -282,6 +296,7 @@ export class PerformController { for (const e of this.performList) { this.clearPerformTimeout(e); this.stopStartedPerform(e); + this.performCommitOptions.delete(e); } this.performList = []; } @@ -300,7 +315,13 @@ export class PerformController { perform.isStarted = false; } - private goNextWhenOver = () => { + private takeCommitOptions(perform: IPerform): IStageCommitOptions { + const commitOptions = this.performCommitOptions.get(perform) ?? {}; + this.performCommitOptions.delete(perform); + return commitOptions; + } + + private goNextWhenOver = (commitOptions: IStageCommitOptions = {}) => { let isBlockingNext = false; this.performList?.forEach((e) => { if (e.blockingNext()) @@ -309,9 +330,9 @@ export class PerformController { }); if (isBlockingNext) { // 有阻塞,提前结束 - setTimeout(this.goNextWhenOver, 100); + setTimeout(() => this.goNextWhenOver(commitOptions), 100); } else { - nextSentence(); + nextSentence(commitOptions); } }; } diff --git a/packages/webgal/src/Core/Modules/readHistory.ts b/packages/webgal/src/Core/Modules/readHistory.ts index 35faaf078..dd002851d 100644 --- a/packages/webgal/src/Core/Modules/readHistory.ts +++ b/packages/webgal/src/Core/Modules/readHistory.ts @@ -6,14 +6,15 @@ import { webgalStore } from "@/store/store"; import { SceneManager } from "./scene"; import { setReadHistory } from "@/store/userDataReducer"; import { setStorage } from "../controller/storage/storageController"; +import type { IStageCommitOptions } from '@/Core/Modules/stage/stageStateManager'; import { stageStateManager } from '@/Core/Modules/stage/stageStateManager'; let debugTextReadMode: boolean | null = null; -export function setDebugTextReadMode(isRead: boolean | null) { +export function setDebugTextReadMode(isRead: boolean | null, commitOptions: IStageCommitOptions = {}) { debugTextReadMode = isRead; if (isRead !== null) { - stageStateManager.setStageAndCommit('isRead', isRead); + stageStateManager.setStageAndCommit('isRead', isRead, commitOptions); } } diff --git a/packages/webgal/src/Core/Modules/stage/stageStateManager.ts b/packages/webgal/src/Core/Modules/stage/stageStateManager.ts index c71b5eb55..875ba105a 100644 --- a/packages/webgal/src/Core/Modules/stage/stageStateManager.ts +++ b/packages/webgal/src/Core/Modules/stage/stageStateManager.ts @@ -23,12 +23,14 @@ export interface IStageCommitOptions { syncPixiStage?: boolean; applyPixiEffects?: boolean; notifyReact?: boolean; + autoFastSave?: boolean; } export interface IResolvedStageCommitOptions { syncPixiStage: boolean; applyPixiEffects: boolean; notifyReact: boolean; + autoFastSave: boolean; } type StageCommitHandler = (stageState: IStageState, options: IResolvedStageCommitOptions) => void; @@ -104,35 +106,39 @@ export class StageStateManager { this.calculationStageState[key] = value; } - public setStageAndCommit(key: K, value: IStageState[K]) { + public setStageAndCommit( + key: K, + value: IStageState[K], + options: IStageCommitOptions = {}, + ) { this.setStage(key, value); - this.commit(); + this.commit(options); } public setStageVar(payload: ISetGameVar) { this.calculationStageState.GameVar[payload.key] = payload.value; } - public setStageVarAndCommit(payload: ISetGameVar) { + public setStageVarAndCommit(payload: ISetGameVar, options: IStageCommitOptions = {}) { this.setStageVar(payload); - this.commit(); + this.commit(options); } public replaceCalculationStageState(stageState: IStageState) { this.calculationStageState = cloneDeep(stageState); } - public replaceAllStageState(stageState: IStageState) { + public replaceAllStageState(stageState: IStageState, options: IStageCommitOptions = {}) { this.calculationStageState = cloneDeep(stageState); - this.commit(); + this.commit(options); } public resetCalculationStageState(stageState: IStageState) { this.replaceCalculationStageState(stageState); } - public resetAllStageState(stageState: IStageState) { - this.replaceAllStageState(stageState); + public resetAllStageState(stageState: IStageState, options: IStageCommitOptions = {}) { + this.replaceAllStageState(stageState, options); } public updateEffect(payload: IEffect) { @@ -169,9 +175,9 @@ export class StageStateManager { } } - public updateEffectAndCommit(payload: IEffect) { + public updateEffectAndCommit(payload: IEffect, options: IStageCommitOptions = {}) { this.updateEffect(payload); - this.commit(); + this.commit(options); } public removeEffectByTargetId(target: string) { @@ -357,6 +363,7 @@ export class StageStateManager { syncPixiStage: options.syncPixiStage ?? true, applyPixiEffects: options.applyPixiEffects ?? true, notifyReact: options.notifyReact ?? true, + autoFastSave: options.autoFastSave ?? true, }; this.viewStageState = cloneDeep(this.calculationStageState); this.commitHandler?.(this.viewStageState, resolvedOptions); @@ -370,6 +377,7 @@ export class StageStateManager { syncPixiStage: false, applyPixiEffects: true, notifyReact: false, + autoFastSave: false, }); } diff --git a/packages/webgal/src/Core/controller/gamePlay/nextSentence.ts b/packages/webgal/src/Core/controller/gamePlay/nextSentence.ts index ab226a8c9..bffbbd24d 100644 --- a/packages/webgal/src/Core/controller/gamePlay/nextSentence.ts +++ b/packages/webgal/src/Core/controller/gamePlay/nextSentence.ts @@ -3,12 +3,13 @@ import { logger } from '../../util/logger'; import { webgalStore } from '@/store/store'; import { WebGAL } from '@/Core/WebGAL'; +import type { IStageCommitOptions } from '@/Core/Modules/stage/stageStateManager'; import { stageStateManager } from '@/Core/Modules/stage/stageStateManager'; /** * 步进前工作:检查阻塞,并在当前演出未完成时提前结束普通演出。 */ -export const preForward = () => { +export const preForward = (commitOptions: IStageCommitOptions = {}) => { if (WebGAL.sceneManager.lockSceneWrite) { logger.warn('next 被场景切换阻塞!'); return false; @@ -22,7 +23,7 @@ export const preForward = () => { const hasUnsettledNonHoldPerform = WebGAL.gameplay.performController.hasUnsettledNonHoldPerform(); if (hasUnsettledNonHoldPerform) { logger.debug('提前结束被触发,现在清除普通演出'); - WebGAL.gameplay.performController.settleNonHoldPerforms(); + WebGAL.gameplay.performController.settleNonHoldPerforms(commitOptions); return false; } @@ -32,7 +33,7 @@ export const preForward = () => { /** * 执行一条语句或由 -next 连接的语句序列,只修改演算状态并收集演出。 */ -export const forward = () => { +export const forward = (commitOptions: IStageCommitOptions = {}) => { if (WebGAL.sceneManager.lockSceneWrite) { logger.warn('forward 被场景切换阻塞!'); return false; @@ -47,7 +48,7 @@ export const forward = () => { WebGAL.gameplay.performController.clearNonHoldPerformsFromStageState(); WebGAL.gameplay.performController.beginCollectingPerforms(); try { - scriptExecutor(); + scriptExecutor(0, commitOptions); } finally { WebGAL.gameplay.performController.endCollectingPerforms(); } @@ -57,8 +58,8 @@ export const forward = () => { /** * 将演算状态提交到当前视图状态,并启动本序列收集到的演出。 */ -export const commitForward = () => { - stageStateManager.commit({ applyPixiEffects: false }); +export const commitForward = (options: IStageCommitOptions = {}) => { + stageStateManager.commit({ ...options, applyPixiEffects: false }); WebGAL.gameplay.performController.commitPendingPerforms(); stageStateManager.applyCommittedPixiEffects(); }; @@ -66,7 +67,7 @@ export const commitForward = () => { /** * 用户操作步进。 */ -export const nextSentence = () => { +export const nextSentence = (commitOptions: IStageCommitOptions = {}) => { WebGAL.events.userInteractNext.emit(); const GUIState = webgalStore.getState().GUI; @@ -74,10 +75,10 @@ export const nextSentence = () => { return; } - if (!preForward()) { + if (!preForward(commitOptions)) { return; } - forward(); - commitForward(); + forward(commitOptions); + commitForward(commitOptions); }; diff --git a/packages/webgal/src/Core/controller/gamePlay/runScript.ts b/packages/webgal/src/Core/controller/gamePlay/runScript.ts index 6ee72e290..b4192764c 100644 --- a/packages/webgal/src/Core/controller/gamePlay/runScript.ts +++ b/packages/webgal/src/Core/controller/gamePlay/runScript.ts @@ -3,17 +3,18 @@ import { initPerform, IPerform } from '@/Core/Modules/perform/performInterface'; import { WebGAL } from '@/Core/WebGAL'; import { scriptRegistry, SCRIPT_TAG_MAP, ScriptFunction } from '@/Core/parser/sceneParser'; +import type { IStageCommitOptions } from '@/Core/Modules/stage/stageStateManager'; /** * 语句调用器,真正执行语句的调用,并自动将演出在指定时间卸载 * @param script 调用的语句 */ -export const runScript = (script: ISentence) => { +export const runScript = (script: ISentence, commitOptions: IStageCommitOptions = {}) => { let perform: IPerform = initPerform; const funcToRun: ScriptFunction = scriptRegistry[script.command]?.scriptFunction ?? SCRIPT_TAG_MAP.say.scriptFunction; // 默认是say // 调用脚本对应的函数 - perform = funcToRun(script); + perform = funcToRun(script, commitOptions); - WebGAL.gameplay.performController.arrangeNewPerform(perform, script); + WebGAL.gameplay.performController.arrangeNewPerform(perform, script, true, commitOptions); }; diff --git a/packages/webgal/src/Core/controller/gamePlay/scriptExecutor.ts b/packages/webgal/src/Core/controller/gamePlay/scriptExecutor.ts index 8bfbb5cd8..faecfdd60 100644 --- a/packages/webgal/src/Core/controller/gamePlay/scriptExecutor.ts +++ b/packages/webgal/src/Core/controller/gamePlay/scriptExecutor.ts @@ -10,6 +10,7 @@ import { ISceneEntry } from '@/Core/Modules/scene'; import { WebGAL } from '@/Core/WebGAL'; import { getBooleanArgByKey, getStringArgByKey } from '@/Core/util/getSentenceArg'; import { stageStateManager } from '@/Core/Modules/stage/stageStateManager'; +import type { IStageCommitOptions } from '@/Core/Modules/stage/stageStateManager'; import { jumpToLabel } from '@/Core/gameScripts/label/jumpToLabel'; import { prefetchCurrentSceneByProgress } from '@/Core/util/prefetcher/progressPrefetcher'; @@ -39,7 +40,7 @@ export const whenChecker = (whenValue: string | undefined): boolean => { * 语句执行器 * 执行语句,同步场景状态,并根据情况立即执行下一句或者加入backlog */ -export const scriptExecutor = (depth = 0) => { +export const scriptExecutor = (depth = 0, commitOptions: IStageCommitOptions = {}) => { if (depth > MAX_FORWARD_SCRIPT_EXECUTION) { logger.error('forward 中执行的语句数量超过限制,可能存在 jumpLabel 或 -next 死循环'); return; @@ -54,7 +55,7 @@ export const scriptExecutor = (depth = 0) => { if (WebGAL.sceneManager.sceneData.sceneStack.length !== 0 && !WebGAL.sceneManager.lockSceneWrite) { const sceneToRestore: ISceneEntry | undefined = WebGAL.sceneManager.sceneData.sceneStack.pop(); if (sceneToRestore !== undefined) { - restoreScene(sceneToRestore); + restoreScene(sceneToRestore, commitOptions); } } return; @@ -104,7 +105,7 @@ export const scriptExecutor = (depth = 0) => { if (!runThis) { logger.warn('不满足条件,跳过本句!'); WebGAL.sceneManager.sceneData.currentSentenceId++; - scriptExecutor(depth + 1); + scriptExecutor(depth + 1, commitOptions); return; } @@ -115,12 +116,12 @@ export const scriptExecutor = (depth = 0) => { logger.warn(`未找到标签 ${currentScript.content},跳过 jumpLabel`); WebGAL.sceneManager.sceneData.currentSentenceId++; } - scriptExecutor(depth + 1); + scriptExecutor(depth + 1, commitOptions); return; } WebGAL.readHistoryManager.checkIsRead(); - runScript(currentScript); + runScript(currentScript, commitOptions); // 是否要进行下一句 let isNext = getBooleanArgByKey(currentScript, 'next') ?? false; @@ -148,7 +149,7 @@ export const scriptExecutor = (depth = 0) => { if (isNext && !hasPendingBlockingStateCalculationPerform && !WebGAL.sceneManager.lockSceneWrite) { WebGAL.sceneManager.sceneData.currentSentenceId++; saveBacklogIfNeeded(); - scriptExecutor(depth + 1); + scriptExecutor(depth + 1, commitOptions); return; } diff --git a/packages/webgal/src/Core/controller/scene/callScene.ts b/packages/webgal/src/Core/controller/scene/callScene.ts index 66ac09e04..086197235 100644 --- a/packages/webgal/src/Core/controller/scene/callScene.ts +++ b/packages/webgal/src/Core/controller/scene/callScene.ts @@ -3,6 +3,7 @@ import { sceneParser } from '../../parser/sceneParser'; import { logger } from '../../util/logger'; import { nextSentence } from '@/Core/controller/gamePlay/nextSentence'; import { clearPrefetchLinks } from '@/Core/util/prefetcher/assetsPrefetcher'; +import type { IStageCommitOptions } from '@/Core/Modules/stage/stageStateManager'; import { WebGAL } from '@/Core/WebGAL'; @@ -11,7 +12,7 @@ import { WebGAL } from '@/Core/WebGAL'; * @param sceneUrl 场景路径 * @param sceneName 场景名称 */ -export const callScene = (sceneUrl: string, sceneName: string) => { +export const callScene = (sceneUrl: string, sceneName: string, commitOptions: IStageCommitOptions = {}) => { if (WebGAL.sceneManager.lockSceneWrite) { return; } @@ -43,7 +44,7 @@ export const callScene = (sceneUrl: string, sceneName: string) => { WebGAL.sceneManager.sceneWritePromise = null; } if (shouldAutoNext) { - nextSentence(); + nextSentence(commitOptions); } }); WebGAL.sceneManager.sceneWritePromise = sceneWritePromise; diff --git a/packages/webgal/src/Core/controller/scene/changeScene.ts b/packages/webgal/src/Core/controller/scene/changeScene.ts index 7a6c1c981..708d60700 100644 --- a/packages/webgal/src/Core/controller/scene/changeScene.ts +++ b/packages/webgal/src/Core/controller/scene/changeScene.ts @@ -3,6 +3,7 @@ import { sceneParser } from '../../parser/sceneParser'; import { logger } from '../../util/logger'; import { nextSentence } from '@/Core/controller/gamePlay/nextSentence'; import { clearPrefetchLinks } from '@/Core/util/prefetcher/assetsPrefetcher'; +import type { IStageCommitOptions } from '@/Core/Modules/stage/stageStateManager'; import { WebGAL } from '@/Core/WebGAL'; @@ -11,7 +12,7 @@ import { WebGAL } from '@/Core/WebGAL'; * @param sceneUrl 场景路径 * @param sceneName 场景名称 */ -export const changeScene = (sceneUrl: string, sceneName: string) => { +export const changeScene = (sceneUrl: string, sceneName: string, commitOptions: IStageCommitOptions = {}) => { if (WebGAL.sceneManager.lockSceneWrite) { return; } @@ -37,7 +38,7 @@ export const changeScene = (sceneUrl: string, sceneName: string) => { WebGAL.sceneManager.sceneWritePromise = null; } if (shouldAutoNext) { - nextSentence(); + nextSentence(commitOptions); } }); WebGAL.sceneManager.sceneWritePromise = sceneWritePromise; diff --git a/packages/webgal/src/Core/controller/scene/restoreScene.ts b/packages/webgal/src/Core/controller/scene/restoreScene.ts index d1d671e6a..e427510cd 100644 --- a/packages/webgal/src/Core/controller/scene/restoreScene.ts +++ b/packages/webgal/src/Core/controller/scene/restoreScene.ts @@ -3,6 +3,7 @@ import { sceneParser } from '../../parser/sceneParser'; import { logger } from '../../util/logger'; import { nextSentence } from '@/Core/controller/gamePlay/nextSentence'; import { ISceneEntry } from '@/Core/Modules/scene'; +import type { IStageCommitOptions } from '@/Core/Modules/stage/stageStateManager'; import { WebGAL } from '@/Core/WebGAL'; @@ -10,7 +11,7 @@ import { WebGAL } from '@/Core/WebGAL'; * 恢复场景 * @param entry 场景入口 */ -export const restoreScene = (entry: ISceneEntry) => { +export const restoreScene = (entry: ISceneEntry, commitOptions: IStageCommitOptions = {}) => { if (WebGAL.sceneManager.lockSceneWrite) { return; } @@ -34,7 +35,7 @@ export const restoreScene = (entry: ISceneEntry) => { WebGAL.sceneManager.sceneWritePromise = null; } if (shouldAutoNext) { - nextSentence(); + nextSentence(commitOptions); } }); WebGAL.sceneManager.sceneWritePromise = sceneWritePromise; diff --git a/packages/webgal/src/Core/controller/stage/resetStage.ts b/packages/webgal/src/Core/controller/stage/resetStage.ts index 7b9af34c9..849028663 100644 --- a/packages/webgal/src/Core/controller/stage/resetStage.ts +++ b/packages/webgal/src/Core/controller/stage/resetStage.ts @@ -1,9 +1,10 @@ import cloneDeep from 'lodash/cloneDeep'; import { WebGAL } from '@/Core/WebGAL'; +import type { IStageCommitOptions } from '@/Core/Modules/stage/stageStateManager'; import { initState, stageStateManager } from '@/Core/Modules/stage/stageStateManager'; import { stopFast } from '@/Core/controller/gamePlay/fastSkip'; -export const resetStage = (resetBacklog: boolean, resetSceneAndVar = true) => { +export const resetStage = (resetBacklog: boolean, resetSceneAndVar = true, commitOptions: IStageCommitOptions = {}) => { /** * 清空运行时 */ @@ -24,8 +25,8 @@ export const resetStage = (resetBacklog: boolean, resetSceneAndVar = true) => { // 清空舞台状态表 const initSceneDataCopy = cloneDeep(initState); const currentVars = stageStateManager.getCalculationStageState().GameVar; - stageStateManager.resetAllStageState(initSceneDataCopy); + stageStateManager.resetAllStageState(initSceneDataCopy, commitOptions); if (!resetSceneAndVar) { - stageStateManager.setStageAndCommit('GameVar', currentVars); + stageStateManager.setStageAndCommit('GameVar', currentVars, commitOptions); } }; diff --git a/packages/webgal/src/Core/gameScripts/callSceneScript.ts b/packages/webgal/src/Core/gameScripts/callSceneScript.ts index e7f9a5848..aadfe0b43 100644 --- a/packages/webgal/src/Core/gameScripts/callSceneScript.ts +++ b/packages/webgal/src/Core/gameScripts/callSceneScript.ts @@ -1,14 +1,15 @@ import { ISentence } from '@/Core/controller/scene/sceneInterface'; import { createNonePerform, IPerform } from '@/Core/Modules/perform/performInterface'; import { callScene } from '../controller/scene/callScene'; +import type { IStageCommitOptions } from '@/Core/Modules/stage/stageStateManager'; /** * 调用一个场景,在场景结束后回到调用这个场景的父场景。 * @param sentence */ -export const callSceneScript = (sentence: ISentence): IPerform => { +export const callSceneScript = (sentence: ISentence, commitOptions: IStageCommitOptions = {}): IPerform => { const sceneNameArray: Array = sentence.content.split('/'); const sceneName = sceneNameArray[sceneNameArray.length - 1]; - callScene(sentence.content, sceneName); + callScene(sentence.content, sceneName, commitOptions); return createNonePerform({ isHoldOn: true }); }; diff --git a/packages/webgal/src/Core/gameScripts/changeSceneScript.ts b/packages/webgal/src/Core/gameScripts/changeSceneScript.ts index ed50f8fb2..9cad68bf8 100644 --- a/packages/webgal/src/Core/gameScripts/changeSceneScript.ts +++ b/packages/webgal/src/Core/gameScripts/changeSceneScript.ts @@ -1,14 +1,15 @@ import { ISentence } from '@/Core/controller/scene/sceneInterface'; import { createNonePerform, IPerform } from '@/Core/Modules/perform/performInterface'; import { changeScene } from '../controller/scene/changeScene'; +import type { IStageCommitOptions } from '@/Core/Modules/stage/stageStateManager'; /** * 切换场景。在场景结束后不会回到父场景。 * @param sentence */ -export const changeSceneScript = (sentence: ISentence): IPerform => { +export const changeSceneScript = (sentence: ISentence, commitOptions: IStageCommitOptions = {}): IPerform => { const sceneNameArray: Array = sentence.content.split('/'); const sceneName = sceneNameArray[sceneNameArray.length - 1]; - changeScene(sentence.content, sceneName); + changeScene(sentence.content, sceneName, commitOptions); return createNonePerform({ isHoldOn: true }); }; diff --git a/packages/webgal/src/Core/gameScripts/choose/index.tsx b/packages/webgal/src/Core/gameScripts/choose/index.tsx index c803a8507..cff088d65 100644 --- a/packages/webgal/src/Core/gameScripts/choose/index.tsx +++ b/packages/webgal/src/Core/gameScripts/choose/index.tsx @@ -14,6 +14,7 @@ import useApplyStyle from '@/hooks/useApplyStyle'; import { Provider } from 'react-redux'; import { useFontFamily } from '@/hooks/useFontFamily'; import { getNumberArgByKey } from '@/Core/util/getSentenceArg'; +import type { IStageCommitOptions } from '@/Core/Modules/stage/stageStateManager'; class ChooseOption { /** @@ -55,14 +56,14 @@ class ChooseOption { * 显示选择枝 * @param sentence */ -export const choose = (sentence: ISentence): IPerform => { +export const choose = (sentence: ISentence, commitOptions: IStageCommitOptions = {}): IPerform => { const chooseOptionScripts = sentence.content.split(/(? ChooseOption.parse(e.trim())); const defaultChoose = getNumberArgByKey(sentence, 'defaultChoose'); const defaultPreviewChoice = getDefaultPreviewChoice(chooseOptions, defaultChoose); if (defaultPreviewChoice) { - selectChooseOption(defaultPreviewChoice, false); + selectChooseOption(defaultPreviewChoice, false, commitOptions); if (!defaultPreviewChoice.jumpToScene) { // The default preview choice is resolved during script calculation. // Let scriptExecutor continue from the target label in this same forward. @@ -79,7 +80,7 @@ export const choose = (sentence: ISentence): IPerform => { // eslint-disable-next-line react/no-deprecated ReactDOM.render( - + , document.getElementById('chooseContainer'), ); @@ -110,15 +111,15 @@ function getDefaultPreviewChoice(chooseOptions: ChooseOption[], defaultChoose: n return defaultOption; } -function selectChooseOption(option: ChooseOption, autoNext = true) { +function selectChooseOption(option: ChooseOption, autoNext = true, commitOptions: IStageCommitOptions = {}) { if (option.jumpToScene) { - changeScene(option.jump, option.text); + changeScene(option.jump, option.text, commitOptions); } else { - jmp(option.jump, autoNext); + jmp(option.jump, autoNext, commitOptions); } } -function Choose(props: { chooseOptions: ChooseOption[] }) { +function Choose(props: { chooseOptions: ChooseOption[]; commitOptions: IStageCommitOptions }) { const font = useFontFamily(); const { playSeEnter, playSeClick } = useSEByWebgalStore(); const applyStyle = useApplyStyle('choose'); @@ -135,7 +136,7 @@ function Choose(props: { chooseOptions: ChooseOption[] }) { ? () => { playSeClick(); WebGAL.gameplay.performController.unmountPerform('choose'); - selectChooseOption(e); + selectChooseOption(e, true, props.commitOptions); } : () => {}; return ( diff --git a/packages/webgal/src/Core/gameScripts/getUserInput/index.tsx b/packages/webgal/src/Core/gameScripts/getUserInput/index.tsx index c58780518..e65cc1603 100644 --- a/packages/webgal/src/Core/gameScripts/getUserInput/index.tsx +++ b/packages/webgal/src/Core/gameScripts/getUserInput/index.tsx @@ -14,12 +14,13 @@ import { logger } from '@/Core/util/logger'; import { tryToRegex } from '@/Core/util/global'; import { showGlogalDialog } from '@/UI/GlobalDialog/GlobalDialog'; import { stageStateManager } from '@/Core/Modules/stage/stageStateManager'; +import type { IStageCommitOptions } from '@/Core/Modules/stage/stageStateManager'; /** * 显示选择枝 * @param sentence */ -export const getUserInput = (sentence: ISentence): IPerform => { +export const getUserInput = (sentence: ISentence, commitOptions: IStageCommitOptions = {}): IPerform => { const varKey = sentence.content.toString().trim(); let title = getStringArgByKey(sentence, 'title') ?? ''; @@ -68,14 +69,17 @@ export const getUserInput = (sentence: ISentence): IPerform => { } } if (userInput) { - stageStateManager.setStageVarAndCommit({ - key: varKey, - value: userInput?.value || defaultValue || ' ', - }); + stageStateManager.setStageVarAndCommit( + { + key: varKey, + value: userInput?.value || defaultValue || ' ', + }, + commitOptions, + ); } playSeClick(); WebGAL.gameplay.performController.unmountPerform('userInput'); - nextSentence(); + nextSentence(commitOptions); }} className={styles.button} > diff --git a/packages/webgal/src/Core/gameScripts/jumpLabel.ts b/packages/webgal/src/Core/gameScripts/jumpLabel.ts index 4388a9379..d36930b71 100644 --- a/packages/webgal/src/Core/gameScripts/jumpLabel.ts +++ b/packages/webgal/src/Core/gameScripts/jumpLabel.ts @@ -1,12 +1,13 @@ import { ISentence } from '@/Core/controller/scene/sceneInterface'; import { createNonePerform, IPerform } from '@/Core/Modules/perform/performInterface'; import { jmp } from '@/Core/gameScripts/label/jmp'; +import type { IStageCommitOptions } from '@/Core/Modules/stage/stageStateManager'; /** * 跳转到指定标签 * @param sentence */ -export const jumpLabel = (sentence: ISentence): IPerform => { - jmp(sentence.content); +export const jumpLabel = (sentence: ISentence, commitOptions: IStageCommitOptions = {}): IPerform => { + jmp(sentence.content, true, commitOptions); return createNonePerform(); }; diff --git a/packages/webgal/src/Core/gameScripts/label/jmp.ts b/packages/webgal/src/Core/gameScripts/label/jmp.ts index 8704b727d..80b18ea84 100644 --- a/packages/webgal/src/Core/gameScripts/label/jmp.ts +++ b/packages/webgal/src/Core/gameScripts/label/jmp.ts @@ -1,9 +1,10 @@ import { nextSentence } from '@/Core/controller/gamePlay/nextSentence'; import { jumpToLabel } from '@/Core/gameScripts/label/jumpToLabel'; +import type { IStageCommitOptions } from '@/Core/Modules/stage/stageStateManager'; -export const jmp = (labelName: string, autoNext = true) => { +export const jmp = (labelName: string, autoNext = true, commitOptions: IStageCommitOptions = {}) => { const isJumped = jumpToLabel(labelName); if (isJumped && autoNext) { - setTimeout(nextSentence, 1); + setTimeout(() => nextSentence(commitOptions), 1); } }; diff --git a/packages/webgal/src/Core/initializeScript.ts b/packages/webgal/src/Core/initializeScript.ts index f6373f6c9..0edaaea0b 100644 --- a/packages/webgal/src/Core/initializeScript.ts +++ b/packages/webgal/src/Core/initializeScript.ts @@ -60,7 +60,7 @@ export const initializeScript = (): void => { WebGAL.gameplay.pixiStage = new PixiStage(); stageStateManager.setCommitHandler((stageState, options) => { syncPixiStageState(stageState, options); - if (options.notifyReact) autoFastSaveGame(); + if (options.autoFastSave) autoFastSaveGame(); }); /** diff --git a/packages/webgal/src/Core/parser/utils.ts b/packages/webgal/src/Core/parser/utils.ts index 2c3ee5b3e..31f8883f3 100644 --- a/packages/webgal/src/Core/parser/utils.ts +++ b/packages/webgal/src/Core/parser/utils.ts @@ -1,11 +1,12 @@ import { commandType, ISentence } from '@/Core/controller/scene/sceneInterface'; import { IPerform } from '@/Core/Modules/perform/performInterface'; +import type { IStageCommitOptions } from '@/Core/Modules/stage/stageStateManager'; /** * 规范函数的类型 * @type {(sentence: ISentence) => IPerform} */ -export type ScriptFunction = (sentence: ISentence) => IPerform; +export type ScriptFunction = (sentence: ISentence, commitOptions?: IStageCommitOptions) => IPerform; export interface ScriptConfig { scriptType: commandType; diff --git a/packages/webgal/src/Core/util/syncWithEditor/previewSyncRuntime.ts b/packages/webgal/src/Core/util/syncWithEditor/previewSyncRuntime.ts index d9fc83b00..4b8579add 100644 --- a/packages/webgal/src/Core/util/syncWithEditor/previewSyncRuntime.ts +++ b/packages/webgal/src/Core/util/syncWithEditor/previewSyncRuntime.ts @@ -174,7 +174,7 @@ export const startPreviewSyncRuntime = () => { applyPreviewDebugVariables(payload.debugVariables); const scene = WebgalParser.parse(payload.snippet, 'temp.txt', 'temp.txt'); (scene.sentenceList as unknown as ISentence[]).forEach((sentence) => { - runScript(sentence); + runScript(sentence, { autoFastSave: false }); }); }; @@ -204,7 +204,7 @@ export const startPreviewSyncRuntime = () => { const handleRunSceneContent = (payload: RunSceneContentPayload) => { setEffectBaselines.clear(); - resetStage(true); + resetStage(true, true, { autoFastSave: false }); applyPreviewDebugVariables(payload.debugVariables); WebGAL.sceneManager.sceneData.currentScene = sceneParser(payload.sceneContent, 'temp', './temp.txt'); applyComponentVisibility({ @@ -214,7 +214,7 @@ export const startPreviewSyncRuntime = () => { showPanicOverlay: false, }); setTimeout(() => { - nextSentence(); + nextSentence({ autoFastSave: false }); }, 100); }; @@ -227,7 +227,7 @@ export const startPreviewSyncRuntime = () => { }; const handleSetTextReadMode = (payload: SetTextReadModePayload) => { - setDebugTextReadMode(payload.isRead); + setDebugTextReadMode(payload.isRead, { autoFastSave: false }); }; const getSetEffectBaseline = (target: string): ITransform => { @@ -247,10 +247,13 @@ export const startPreviewSyncRuntime = () => { const handleSetEffect = (payload: SetEffectPayload) => { const newTransform = mergeSetEffectPreviewTransform(getSetEffectBaseline(payload.target), payload.transform); WebGAL.gameplay.pixiStage?.removeAnimationByTargetKey(payload.target); - stageStateManager.updateEffectAndCommit({ - target: payload.target, - transform: newTransform, - }); + stageStateManager.updateEffectAndCommit( + { + target: payload.target, + transform: newTransform, + }, + { autoFastSave: false }, + ); }; const previewRequestHandlers: { diff --git a/packages/webgal/src/Core/util/syncWithEditor/runtime/previewSyncSceneCommand.ts b/packages/webgal/src/Core/util/syncWithEditor/runtime/previewSyncSceneCommand.ts index 36f8ae0e5..88c788fa1 100644 --- a/packages/webgal/src/Core/util/syncWithEditor/runtime/previewSyncSceneCommand.ts +++ b/packages/webgal/src/Core/util/syncWithEditor/runtime/previewSyncSceneCommand.ts @@ -38,7 +38,7 @@ export function executePreviewSyncSceneCommand( sceneFetcher(sceneUrl) .then((rawScene) => { - resetStage(true); + resetStage(true, true, { autoFastSave: false }); applyPreviewDebugVariables(debugVariables); WebGAL.sceneManager.sceneData.currentScene = sceneParser(rawScene, sceneName, sceneUrl); const currentSceneName = WebGAL.sceneManager.sceneData.currentScene.sceneName; @@ -69,7 +69,7 @@ export async function runFastPreview( while (shouldContinueFastPreview(sentenceId, currentSceneName, baseSceneStackDepth)) { const prevSentenceId = WebGAL.sceneManager.sceneData.currentSentenceId; const prevSceneName = WebGAL.sceneManager.sceneData.currentScene.sceneName; - const isForwarded = forward(); + const isForwarded = forward({ autoFastSave: false }); forwardCount++; const sceneWriteWaitStart = performance.now(); const awaitedSceneWrite = await waitForPendingSceneWrite(); @@ -108,7 +108,7 @@ export async function runFastPreview( WebGAL.gameplay.isFastPreview = false; } - commitForward(); + commitForward({ autoFastSave: false }); const forwardedLineCount = WebGAL.sceneManager.sceneData.currentScene.sceneName === currentSceneName From 1e554695dd4812b30603490710eb52fe9a88ecee Mon Sep 17 00:00:00 2001 From: Akirami <66513481+A-kirami@users.noreply.github.com> Date: Thu, 18 Jun 2026 00:52:47 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20=E9=9D=99=E9=BB=98=E6=8F=90=E4=BA=A4?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E4=B8=8D=E8=A7=A6=E5=8F=91=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E5=BF=AB=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../webgal/src/Core/Modules/stage/stageStateManager.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/webgal/src/Core/Modules/stage/stageStateManager.ts b/packages/webgal/src/Core/Modules/stage/stageStateManager.ts index 875ba105a..db1804e6e 100644 --- a/packages/webgal/src/Core/Modules/stage/stageStateManager.ts +++ b/packages/webgal/src/Core/Modules/stage/stageStateManager.ts @@ -350,7 +350,9 @@ export class StageStateManager { } public clearUncommittedNonHoldPerforms() { - this.calculationStageState.PerformList = this.calculationStageState.PerformList.filter((perform) => perform.isHoldOn); + this.calculationStageState.PerformList = this.calculationStageState.PerformList.filter( + (perform) => perform.isHoldOn, + ); } public removeNonHoldPerformsAndCommit() { @@ -359,11 +361,12 @@ export class StageStateManager { } public commit(options: IStageCommitOptions = {}) { + const notifyReact = options.notifyReact ?? true; const resolvedOptions: IResolvedStageCommitOptions = { syncPixiStage: options.syncPixiStage ?? true, applyPixiEffects: options.applyPixiEffects ?? true, - notifyReact: options.notifyReact ?? true, - autoFastSave: options.autoFastSave ?? true, + notifyReact, + autoFastSave: options.autoFastSave ?? notifyReact, }; this.viewStageState = cloneDeep(this.calculationStageState); this.commitHandler?.(this.viewStageState, resolvedOptions);