Skip to content

实现新流程控制和演出调用架构#937

Merged
MakinoharaShoko merged 20 commits into
devfrom
refactor-state-mgmt
May 3, 2026
Merged

实现新流程控制和演出调用架构#937
MakinoharaShoko merged 20 commits into
devfrom
refactor-state-mgmt

Conversation

@MakinoharaShoko
Copy link
Copy Markdown
Member

本 PR 主要实现新的流程控制和演出调用架构。

该架构将 WebGAL 的舞台与演出系统彻底从 Redux 中剥离,避免状态订阅造成的异步挂载舞台对象导致的动画作用时序问题。同时,也实现了计算状态与舞台状态的剥离,可大幅改善快进和与编辑器联动的快速预览的性能。

关于本 PR 的主要思想,请参考 RFC1:WebGAL 5 流程控制和演出调用草案 中的内容。

除此以外,本 PR 还将演出脚本的实现分为对舞台状态的改变部分和演出调用部分。对舞台状态的改变直接写在演出脚本的实现主函数中,而需要启动的演出则写在返回给演出管理器的 startFunction 中。这样就可以实现修改状态和启动演出的分离。在快进模式下或快速预览时,可以跳过大量无需被启动的演出。

关于 PR 的主要测试重点:

  • 对于复杂的动画,包括跨语句动画和并发动画,能否良好地支持,并保持和主线相同的行为。
  • 对于回溯、存读档,能否正确恢复状态。
  • 对于正常游玩、自动、快进和快速预览模式,在游戏进行到相同的进度时,能否保证状态的一致。

对于本 PR,有诸多一致性校验的工作要做,但是合并本 PR 将可以彻底解决舞台状态被 Redux 掣肘和快速预览的性能问题,让快速预览功能真正实现“指哪打哪”的流畅效果,收益丰厚。

在本 PR 合并后,WebGAL 将进入 4.6 版本。

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the stage state management by introducing a StageStateManager class to replace the previous Redux-based state management for stage-related data. This change involves migrating state updates, effect management, and perform collection logic to the new manager, improving performance and decoupling state from the UI layer. I have identified a few potential issues: the playVideo function references undefined variables bgmVol and vocalVol in restoreVolumeAndUnmount, the setTimeout in performController.ts could lead to closure issues, and the error handler in playEffect.ts needs to ensure the perform is properly unmounted.

Comment on lines +23 to +38
const restoreVolumeAndUnmount = () => {
WebGAL.events.fullscreenDbClick.off(skipVideo);
/**
* 恢复音量
*/
const bgmElement: any = document.getElementById('currentBgm');
if (bgmElement) {
bgmElement.volume = bgmVol.toString();
}
const vocalElement: any = document.getElementById('currentVocal');
if (vocalElement) {
vocalElement.volume = vocalVol.toString();
}
// eslint-disable-next-line react/no-deprecated
ReactDOM.render(<div />, document.getElementById('videoContainer'));
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The restoreVolumeAndUnmount function is defined inside playVideo and captures bgmVol and vocalVol from the outer scope. However, these variables are not defined in the provided snippet. Please ensure these variables are correctly defined and accessible in this scope.

Comment on lines +145 to 152
const stopTimeout = setTimeout(() => {
// perform.stopFunction();
// perform.isOver = true;
if (!perform.isHoldOn) {
// 如果不是保持演出,清除
this.softUnmountPerformObject(perform);
}
}, perform.duration);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The setTimeout here creates a closure that captures perform. If perform is updated or replaced in the performList before the timeout fires, this could lead to unexpected behavior. It is safer to use the stopTimeoutMap to manage these timeouts and ensure they are cleared correctly when a perform is stopped or unmounted.

Comment on lines +68 to +71
seElement.addEventListener('error', () => {
logger.error(`播放效果音失败: ${url}`);
endFunc();
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The error handler for the audio element does not call WebGAL.gameplay.performController.unmountPerform(performInitName) to clean up the perform state when an error occurs, which might leave the perform in an inconsistent state.

Suggested change
seElement.addEventListener('error', () => {
logger.error(`播放效果音失败: ${url}`);
endFunc();
});
seElement.addEventListener('error', () => {
logger.error(`播放效果音失败: ${url}`);
endFunc();
WebGAL.gameplay.performController.unmountPerform(performInitName);
});

# Conflicts:
#	packages/webgal/src/Core/gameScripts/wait.ts
#	packages/webgal/src/Stage/MainStage/useSetEffects.ts
@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented May 1, 2026

Deploying webgal-dev with  Cloudflare Pages  Cloudflare Pages

Latest commit: 4cce67f
Status: ✅  Deploy successful!
Preview URL: https://e1a5ec30.webgal-dev.pages.dev
Branch Preview URL: https://refactor-state-mgmt.webgal-dev.pages.dev

View logs

@MakinoharaShoko
Copy link
Copy Markdown
Member Author

测试了 https://github.com/OpenWebGAL/WebGAL/blob/refactor-state-mgmt/packages/webgal/public/game/scene/demo_parallel_animation.txt 脚本,并逐条对话比对通过快速预览模式和正常模式下,抵达同一个对话时,舞台对象的状态。

结论:除了 #938 所属的已有问题外,新内核在一致性上表现良好。并且在快速预览到相当靠后的语句时,再使用回溯来回到某个对话时,舞台对象的状态也正确恢复了,这也意味着,即便不启动演出,状态计算器也在正确的位置计算出了在执行到那条对话时舞台的状态,并保存到 backlog 中。

我们仍需要更多复杂的动画和效果的测试,但是在处理复杂 -parallel 动画时,新内核表现出较强的稳定性。

@MakinoharaShoko
Copy link
Copy Markdown
Member Author

也需要测试存读档和状态恢复等。

@MakinoharaShoko
Copy link
Copy Markdown
Member Author

除了动画系统,是否可以和音效系统良好配合也很重要,尤其是效果音(循环效果音、后效果音打断前效果音),语音中断体系。

# Conflicts:
#	packages/webgal/package.json
#	packages/webgal/public/game/template/template.json
#	packages/webgal/public/webgal-engine.json
#	packages/webgal/src/Core/Modules/scene.ts
#	packages/webgal/src/Core/controller/gamePlay/scriptExecutor.ts
#	packages/webgal/src/Core/gameScripts/vocal/index.ts
@MakinoharaShoko MakinoharaShoko merged commit b46c22a into dev May 3, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant