DOCUMENTATION · 事件总线

事件总线

引擎通过 EventBus 广播事件。本文列出引擎/运行时内置发出的全部事件、payload 结构与来源, 供插件(JS 通过 ctx.events 监听、WASM 通过 host…

同步自 docs/EVENTS.md(162 行) · commit b2b4124 · 读源文件 ↗

权威实现:src/runtime/EventBus.ts、src/runtime/StoryEngine.ts、src/plugins/PluginManager.ts、 src/plugins/WasmPluginLoader.ts、src/runtime/Runtime.ts、src/runtime/WorldLoop.ts。 若本文与源码冲突,以源码为准。


事件元信息(EventMeta)

每次 emit 都会附带元信息(监听器签名 (payload, meta) => void):

interface EventMeta {
  name: string            // 事件名,例如 "story:nodeEnter"
  source: 'engine' | 'ui' | 'plugin' | 'system'   // 来源
  pluginId?: string       // 来源是插件时记录插件 id
}

通配监听 *

监听事件名传 WILDCARD(即 "*")可收到所有事件。

ctx.events.on('*', (payload, meta) => {
  console.log(meta.source, meta.name, payload)
})

监听器隔离

单个监听器抛错不会影响其它监听器;错误会交给 Runtime 注入的 error reporter, 若监听器归属某个插件,还会计入该插件的连续失败次数。


按来源分组的事件清单

1. 剧情引擎事件(source: 'engine')

由 StoryEngine 在主循环推进时广播。

事件名触发时机payload
story:loaded剧情文件解析校验通过、准备运行前{ storyId, title, nodeCount }
engine:start主循环开始{ storyId, nodeId }
story:nodeEnter进入某个节点{ storyId, nodeId, node }(node 为该节点原始定义)
story:nodeExit离开某个节点{ storyId, nodeId, node, result }(result 为处理器返回下一跳信息,无则 null)
story:say渲染对白(say 节点或插件返回的 message){ nodeId, speaker, text, from }(from 仅在插件 message 时为 'plugin')
story:choice用户做出选择{ nodeId, index, text, option }
story:jump发起显式跳转{ from, to }
story:branchif 分支被求值并决定去向{ nodeId, condition, next }
story:waitSkipped可跳过的 wait 被玩家提前跳过nodeId / ms
story:waitOverrun{ nodeId, requestedMs, actualMs, lateByMs }wait 实际等了远超要求(典型原因:标签页被后台限流/冻结 ✗)—— 引擎只报事实,怎么处理由剧情决定
story:observe世界事件触发 observe 节点并跳转{ event, nodeId, next, payload }(nodeId 为 observe 节点 id,next 为跳转目标)
story:observeEnter主循环途经一个 observe 节点(作为普通节点时的兜底){ nodeId, event? }
story:pause剧情被暂停{ nodeId }
story:end剧情结束{ storyId, lastNodeId }
engine:stop主循环真正退出{ storyId, nodeId, ended }
story:error节点/条件/效果执行出错{ storyId?, nodeId?, error, expression? }

插件/剧情主动广播的自定义事件也会以 source: 'engine' 发出(见下文「引擎侧广播自定义事件」)。

2. 插件生命周期事件(source: 'engine')

由 PluginManager 广播。

事件名触发时机payload
plugin:loaded插件加载完成(manifest 解析通过){ info }(info 为 PluginInfo)
plugin:status插件状态变化{ id, status, name? }
plugin:error插件加载/调用出错(含熔断前){ id, error, phase? } 或 { id, error, failures }
plugin:disposed插件被卸载{ id }
plugin:crashed连续失败达到阈值后熔断禁用、回收能力{ id, error }

3. 运行时状态事件(source: 'engine')

事件名触发时机payload
state:changeStateStore 任何写入触发(由 Runtime 转发){ snapshot }(snapshot 为完整 StateSnapshot)

4. 世界循环事件(source: 'engine')

由 WorldLoop 广播,是「世界模拟器」的核心信号:

事件名触发时机payload
world:start世界循环启动(WorldLoop.start()){ intervalMs }
world:stop世界循环停止{ tick }(停止时的累计拍号)
world:pause世界循环暂停(WorldLoop.pause();插件可经 ctx.world.pause() 触发){ tick, reason }(reason 缺省为 null)
world:resume世界循环恢复(WorldLoop.resume()){ tick }
world:tick世界循环每拍广播{ tick, elapsedMs }(tick 自启动起从 1 递增,elapsedMs 为累计毫秒)
engine:quality{ level: 'high'|'medium'|'low', reason: string, policy: QualityPolicy }引擎(性能看门狗)

world:tick 的消费方:

5. 世界业务事件(source: 'plugin',示例)

插件用 ctx.world.publish / ctx.events.emit / WASM host_emit 发布的世界事件,事件名与结构由插件自定义。 引擎不关心语义,只负责广播。以下事件仅示意契约,非内置事件:

事件名触发时机payload
alert:emergency某种阈值告警{ level, targets: string[] }
agent:conflict两名智能体冲突{ a, b, reason }
agent:moved智能体移动位置{ agent, from, to }
task:complete任务完成{ agent, task, reward }
agent:sick智能体状态异常(连续多日不佳){ agent, days }
report:periodic周期性统计报告{ tick, summary }

剧情侧的 observe 节点、JS 插件侧的 ctx.world.subscribe、WASM 插件侧的事件代理可以同时监听同一事件(三路平权)。

6. 插件发出的业务事件(source: 'plugin')

JS 插件用 ctx.events.emit(name, payload)、WASM 插件用 host_emit 广播。事件名/结构由插件自定义,例如 wasm:greeted、story:nodeEnter 的插件侧监听等。

WASM 插件回传的 host_emit 事件由 WasmPluginLoader 转为 source: 'plugin'、pluginId: <manifest.id> 的事件。


引擎侧广播自定义事件

剧情节点的 emit 节点类型,以及剧情文件触发的 plugin 效果,都会以 source: 'engine' 广播到 EventBus。

{ "type": "emit", "name": "my:event", "payload": { "k": "v" } }

引擎效果 { "type": "emit", "name": "...", "payload": {...} } 亦是同样语义。

JS 插件若要触发剧情节点跳转、结束等流程控制,应使用 ctx.story.jumpTo / end(需 story:control 权限),而不是依赖事件。


监听示例

// JS 插件内
ctx.events.on('story:nodeEnter', (p, m) => {
  console.log(`进入节点 ${p.nodeId}(来源 ${m.source})`)
})

ctx.events.on('plugin:crashed', (p) => {
  console.warn(`插件 ${p.id} 已熔断:${p.error}`)
})

相关链接