Skip to content

feat: callScene supports args - #1011

Merged
MakinoharaShoko merged 2 commits into
devfrom
callscene
Jul 29, 2026
Merged

feat: callScene supports args#1011
MakinoharaShoko merged 2 commits into
devfrom
callscene

Conversation

@MakinoharaShoko

Copy link
Copy Markdown
Member

背景

callScene 增加参数传递与返回值,使被调用的场景成为真正的子过程。相关 issue: #421

用法

; start.txt
callScene:battle.txt -enemy=史莱姆 -hp=100 -writeReturnTo=result;
webgal:战斗结果是 {result};

; battle.txt
webgal:遭遇了{enemy},血量{hp};
return:1;
  • -xxx=yyy 原样成为被调用场景的局部变量,没有前缀、没有保留字
  • -writeReturnTo=b 指定返回值写回调用方的哪个变量,b 是普通游戏变量,用法同 setVar
  • return:expr; 立即返回并携带返回值;没有 return 而自然结束时返回空值
  • 变量查找链:当前帧局部变量 → 舞台变量 → 全局变量

设计

引入调用帧的概念:一帧 = 一次 callScene,持有局部变量、返回地址、返回值去向。

关键在于帧的归属:changeScene / choose 不是帧操作,它们只是把当前帧正在读的文本换掉,栈和局部变量都不动。所以在被调用的场景里 changeScene 之后,参数仍然有效,结束时也仍然返回原调用方。这两个命令不支持参数。

实现

帧结构ISceneEntry 增加 locals / writeReturnToISceneData 增加 currentLocals(栈顶之上的当前帧,因为 sceneName/sceneUrl/currentSentenceId 本来就是摊平存放的当前帧)。

场景栈和 currentLocals 是同一个栈的两截,全部经由 SceneManager.pushFrame() / popFrame() 变更,避免两者脱节。场景末尾原本手写的 pop + restore 也收敛到 returnFromScene(),与 return 语句共用一条路径。

参数求值:不引入任何表达式引擎。argsParser 本来就会把 -a=1 解析成数字、-a=true 解析成布尔;只有经过 {} 插值的参数会退化成字符串,在提取参数时按同一套规则还原类型即可。引用变量写 {b},与其他所有参数一致。

存档:场景栈本来就整体 cloneDeep 进存档,多层嵌套的局部变量随帧免费持久化;只有当前帧的 currentLocals 需要单独搬运(saveGame / backlog / loadGame / jumpFromBacklog 各一行)。新字段全部可选,旧存档三处 ?? {} 兜底,可直接读取。

其他

  • commandType.return 追加在枚举末尾——旧存档里 PerformList[].script.command 是持久化的数值,插在中间会错位
  • 场景栈深度上限 64,防止 callScene 无限递归
  • 修掉一个既有隐患:saveGame / fastSaveGame 此前没有 lockSceneWrite 判定,场景写入期间存档会拿到撕裂状态(栈已变更但场景尚未切换)。加了 return 之后这会导致读档重复返回,本 PR 一并加上判定

注意

commandType 有改动,webgal 包的类型检查依赖 parser 的构建产物,需要先构建 parser 再检查 webgal。

验证

两个包 tsc --noEmit、eslint、parser 既有测试全部通过。callScene 参数解析与 return 的四种写法(return; / return:5; / return:; / return:{x}+1;)已实测。

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5b13a39c02

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

sceneUrl: WebGAL.sceneManager.sceneData.currentScene.sceneUrl,
continueLine: WebGAL.sceneManager.sceneData.currentSentenceId,
});
WebGAL.sceneManager.pushFrame(locals, writeReturnTo);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Roll back the frame when scene loading fails

When sceneFetcher rejects—for example because a referenced scene is missing or not a .txt file—pushFrame has already replaced currentLocals, but the catch handler only logs the error. The caller therefore resumes with the callee's arguments as its locals, which can shadow its variables and be persisted in later saves; pop the newly pushed frame in the failure path before unlocking.

Useful? React with 👍 / 👎.

Comment on lines +20 to +21
if (entry.writeReturnTo) {
setGameVar({ key: entry.writeReturnTo, value: returnValue });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Write returns into an existing caller-local target

When a nested caller already has a local with the writeReturnTo name, popFrame() restores that local and this line writes the result only to the stage variable map. Since variable lookup prioritizes currentLocals, subsequent {name} reads still return the caller's old local value and the returned result is inaccessible; update the restored local when that target exists, falling back to normal setVar storage otherwise.

Useful? React with 👍 / 👎.

const _Merge = { stage, userData }; // 不要直接合并到一起,防止可能的键冲突
if (stage.GameVar.hasOwnProperty(key)) {
// 查找链:当前帧局部变量 -> 舞台变量 -> 全局变量
if (locals.hasOwnProperty(key)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Check local ownership without invoking user data

Because every argument name is accepted as a local, a call such as callScene:x.txt -hasOwnProperty=value replaces this method on the plain locals object. Any subsequent interpolation or expression lookup then executes the string as a function and throws; use Object.prototype.hasOwnProperty.call(locals, key) or create a null-prototype map so argument names cannot break lookup.

Useful? React with 👍 / 👎.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 25, 2026

Copy link
Copy Markdown

Deploying webgal-dev with  Cloudflare Pages  Cloudflare Pages

Latest commit: 30bce07
Status: ✅  Deploy successful!
Preview URL: https://35d3859b.webgal-dev.pages.dev
Branch Preview URL: https://callscene.webgal-dev.pages.dev

View logs

@MakinoharaShoko
MakinoharaShoko merged commit c48eddc into dev Jul 29, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant