外观
生命周期
插件继承 Plugin,由 Host 管理进程、验证、启动和停止。把网络访问、游戏动作和定时任务放在激活后的回调中,确保验证候选版本时不会产生业务效果。
回调顺序
| 回调 | 可做什么 |
|---|---|
async on_init(self, ctx) | 同一 Worker 仅调用一次;声明 Bus 成员与受管订阅,不执行一般业务副作用或启动后台任务。 |
async on_validate(self, ctx) | 读取配置、检查已保存数据与业务约束。保持只读,不启动任务。 |
async on_start(self, ctx) | 初始化玩法,注册聊天 handler,启动受管理后台任务。游戏接口此时可用。 |
async on_event(self, ctx, event) | 处理订阅事件并显式确认;默认实现会报错,订阅了事件就要实现。 |
async on_maintenance(self, ctx, request) | 在插件自己的锁内暂停或恢复新业务准入;默认返回 unsupported。 |
async on_stop(self, ctx) | 事件排空及后台任务取消退出后的收尾;不要在此新建任务。 |
声明了 config_type 时,配置在验证回调前加载。1.0.0 SDK 的首次顺序为:加载配置 → on_init → 冻结 Bus 成员与受管订阅声明 → on_validate;激活时再调用 on_start。同一 Worker 再次验证会重新加载配置并调用 on_validate,不重复 on_init;初始化失败也不会在同一 Worker 重试。
Bus 声明与受管订阅应放在 on_init,不能等到 on_start 才注册;激活后可直接打开 ctx.bus.watch。这里的 on_init 与 Bus 规则需使用配套 Host 与 SDK,见原生 Bus。
ctx.operations、ctx.commands、ctx.scoreboard 等游戏入口在激活前不可用。候选验证可以读取旧状态快照;允许候选读取且已获授权的活动 read 不等于一般业务动作可用,也不表示候选已经切换成功。
定时与后台任务
python
from neomega_runtime import Plugin
class CounterPlugin(Plugin):
async def on_start(self, ctx):
async def tick():
count = await ctx.storage.increment("ticks")
ctx.log.info("累计执行次数=%d", count)
ctx.every(60, tick, immediate=False, name="counter")
async def on_stop(self, ctx):
ctx.log.info("定时任务已停止")
if __name__ == "__main__":
CounterPlugin().run()ctx.every(seconds, callback, *, immediate=False, name=None) 要求有限正数秒间隔与不接收参数的异步回调。一次回调完成后才开始下一轮间隔,慢回调不会堆积任务;这不是精确墙钟调度器,也不补跑离线期间的次数。
一次延迟使用 ctx.later(seconds, callback, *, name=None),等待后执行一次无参异步回调;seconds 必须是有限非负数。它与 every 一样返回受 Worker 管理的任务,取消任务即可取消尚未开始的回调,示例见作者便利接口。
一般协作式后台工作使用 ctx.spawn(coroutine, *, name=None),返回 asyncio.Task;只接受新建且尚未运行的 coroutine,不接受已有 Task/Future。验证前、尚未激活或停止排空期间均不能启动后台任务。Worker 默认最多拥有 32 个后台任务,停止时取消并等待它们退出。后台异常会使插件失败,即使调用方正在等待返回的 Task,也不能把异常当作已被自动隔离。
原始 asyncio.create_task、自行启动的线程或子进程不自动纳入这套退出管理。不要在事件循环中执行长时间阻塞计算;释放外部资源使用 try/finally,遇到 asyncio.CancelledError 应完成清理后继续抛出。
更新维护回调
支持维护协议的 Host 在更新已运行插件前,可以调用 async on_maintenance(self, ctx, request)。request 是字典,包含当前 installation_id、generation、operation 和 token:
| 请求 | 返回字典 |
|---|---|
operation="seal" | {"status": "sealed" | "busy" | "unsupported", "token": 原 token}。 |
operation="release" | {"status": "released" | "busy" | "unsupported", "token": 原 token}。 |
token 为 1–256 字符,必须原样返回。sealed 表示插件已在自己的业务锁内关闭新请求入口,并完成更新所要求的在途核对;未能达到这一步就返回 busy,无法实现则返回 unsupported。同 token 的 release 必须幂等,seal 也应按持有的 token 去重;不能让别的 token 解除当前维护锁。
维护回调不会自动排空事件、取消 Worker 后台任务或阻止服务处理器进入。事件回调与后台任务可能同时运行,所以计时器、服务、聊天和其他业务入口必须检查同一准入状态。暂停接受新交易之后,已提交动作仍需查询原 ID,不能因维护而重放。
异常或错误的返回 token/status 会报告 maintenance_failed,但不会因此取消旧插件的后台任务。回调可能已关闭准入才抛错,因此必须让同 token 的 release 能完成恢复。业务维护状态只有插件自己能判断,不能仅返回 sealed 来跳过在途检查。
该回调与正常停止的 on_stop 不同。是否支持维护及如何核对候选制品,以更新计划的返回为准;无支持时不要推断可以无损热更新。
管理动作的区别
| 管理命令 | 效果 |
|---|---|
start NAME / stop NAME | 启动或停止本次运行;stop 不清除持久自动启动设置。 |
enable NAME / disable NAME | 保存启用状态并启动,或停止并取消自动启动。 |
reload NAME | 重新启动当前已安装制品,不收取开发目录中的修改。 |
quit | 只退出插件控制台,保留 Host 与插件运行。 |
安装新包、保存配置、确认权限是启动前的独立步骤。Host 重启按启用记录及依赖顺序恢复;宿主也可配置为不自动启动。查看实际管理状态,不能从一次旧日志推断当前进程仍在线。
重载与持久数据
reload 只适用于当前已安装制品;不同 SHA-256 的新包必须经过更新计划与维护步骤。不要把重载成功理解为开发目录中的新代码已经装入。
当前制品验证失败时保留旧进程;验证通过后,Host 停止旧进程再启动。启动失败会报告错误,需要检查状态和日志;不承诺回滚业务数据。
使用 ctx.storage 保存需跨停止、重载和 Host 重启保留的业务数据。ctx.data_dir 是当前安装的数据目录,普通文件不参与 SQLite 事务。不要将数据目录或真实配置打入发布包。
游戏操作退出边界
取消 Python 的等待任务不等于取消已经提交的游戏动作。ctx.operations.cancel 是显式取消请求,也不回滚部分效果;unknown 必须核对原回执。重载后的新插件不能用新幂等键自动重发旧动作,处理步骤见错误与结果核对。
View 与模块替换
受管订阅在初始化声明,激活后运行;停止请求取消并等待回调与来源实际退出。View 的 instance_replaced 表示原来源结束,消费者按业务策略关闭旧流并重新发现、读取新原子基线,不重放未知间隔。任务取消不撤销已接纳的持久动作。见Bus。