跳转至

📋 脚本引擎 - 特定脚本语言开发须知

🌏 开发语言支持情况

通过 ScriptX 项目的支持,脚本引擎使用同一套源代码,对多种开发语言进行了适配。
同时,由于API保持一致,使各种语言得以共享一份开发文档,大大降低了维护难度。

目前,脚本引擎支持使用如下语言编写插件:

语言后端 备注
JavaScript 使用 QuickJS 引擎运行插件,支持 ES Module 机制
Lua 使用 CLua 引擎运行插件
Node.js 改造 Node.js 使其适合嵌入工作,支持 npm 包管理
Python 使用 CPython 引擎运行插件,支持 pip 包管理

Tip

如果需要使用 C++、Go、Rust 等原生语言编写插件,请移步 主页 查看其他语言文档

JavaScript 语言支持说明

  • 使用 QuickJS 引擎对简单的 JavaScript 插件提供支持,轻量级引擎资源占用较少
  • QuickJS 当前版本支持到 ES2020 语言特性,同时原生支持 ESModule 模块机制,可以方便开发者进行项目管理
  • 暂不支持包管理机制,如果需要请使用 Node.js 进行插件开发,使用 npm 包管理
  • 在BDS控制台中使用jsdebug命令进入和退出 QuickJs 交互式命令行环境。此功能便于编写插件时进行一些简单的测试

关于 QuickJs ESModule 的路径解析行为

LegacyScriptEngine 使用的 ScriptX 来自上游 Tencent/ScriptX 项目的 fork, 并在其基础上增加了一些功能(例如 Python 后端支持等)。

在 QuickJs 后端中,ScriptX 默认使用 quickjs-libc.h 中提供的 module loader。 该 loader 会导致 QuickJS ESModule 在解析入口模块时的行为与标准 ESModule 不完全一致。

具体表现为: 该 loader 在加载 入口模块 时,会将模块的 base path 设为 当前工作目录 (CWD),即服务器程序所在目录,而不是脚本文件所在目录。 因此入口文件中的相对 import 路径解析行为与标准 ESModule 不完全一致。

假设脚本插件的结构为:

Text Only
1
2
3
4
5
6
7
8
c:/bds
    bedrock_server_mod.exe
    plugins/
        your_plugin/
            manifest.json
            index.js
            command/
                command.js

index.js 入口文件:

JavaScript
1
2
3
import { initCommand } from "./command/command.js"

initCommand();

此时 QuickJS 会将入口模块的 import.meta.url 解析为 file:///c:/bds 而不是 file:///c:/bds/plugins/your_plugin

因此 ./command/command.js 会解析失败,导致插件加载失败。

例如 QuickJS 可能会抛出类似如下的错误(原始日志):

Text Only
1
2
19:26:12.343 ERROR [LeviLamina] Failed to load plugin your_plugin: Unexpected token '{'      
19:26:12.343 ERROR [LeviLamina]     at ./plugins/your_plugin\index.js:1:1

解决方法是使用相对于工作目录的路径

JavaScript
1
import { initCommand } from "./plugins/your_plugin/command/command.js"

即可正常加载模块。

Tip

该问题仅影响 manifest.json 指定的入口模块。 入口模块加载完成后,其它模块之间的 import 行为均符合标准 ESModule 规则。

Lua 语言支持说明

  • 使用 CLua 引擎,支持使用 require 进行简单的项目管理
  • 由于 Rocks 包管理机制需要引入编译器,因此暂不提供相关实现。如果需要依赖扩展可以手动编译后引入项目进行使用(如 SQLite 等常用库)
  • 在BDS控制台中使用luadebug命令进入和退出 Lua 交互式命令行环境。此功能便于编写插件时进行一些简单的测试

Node.js 支持说明

  • LSE 通过自行实现 Node.js 启动代码,使其可以在嵌入式模式下工作,并实现了不同插件的执行环境隔离
  • 自行编写接口实现了对 npm 的 programmic 支持。支持通过 package.json 安装第三方扩展依赖包

Node.js 插件开发方法

  1. 首先,安装NodeJs
  2. 创建一个新的目录进行插件开发。在此目录启动终端执行npm init命令。根据 npm 的提示创建新项目,填写项目的相关信息。
  3. 在填写的入口点文件中编写插件代码
  4. 如果有需要,可以创建多个源码文件,并且通过 require 来引入他们。合理的对源码进行分文件拆分编写有助于提高项目结构的清晰度,方便后续对插件进行进一步的维护和扩展。

Node.js 插件打包 & 部署方法

  • 在插件编写完成之后,请将 package.json 以及所有插件源码打包为一个zip压缩包,并将文件名后缀修改为 .llplugin
  • node_modules 目录请勿打包在压缩包之中
  • .llplugin 文件作为插件分发,安装插件时直接将此文件放置到 plugins 目录即可
  • 在开服时,脚本引擎会自动识别 .llplugin 文件,将其解压到plugins/nodejs/插件名目录,并在目录中自动执行 npm install --omit=dev 安装依赖包,整个过程无需人工干预

Python 语言支持说明

  • 使用 CPython 引擎,Python版本为3.12.8。支持使用 pip 包管理机制为插件安装第三方扩展依赖包,支持多文件插件开发和 import,支持现代项目管理机制
  • 在BDS控制台中使用pydebug命令进入和退出 Python 交互式命令行环境。此功能便于编写插件时进行一些简单的测试

Python 单文件插件开发方法

  • 为了方便不熟悉Python的开发者快速上手,我们提供了一种简单的 Python 插件支持:单文件插件。
  • 单文件插件类似QuickJs和Lua插件。只要编写单个.py文件作为插件,将插件直接放置到 plugins目录中,在开服时就会被加载运行
  • 单文件插件缺点:不支持插件元数据储存、不支持源码分文件、不支持pip第三方包。单文件插件仅用于开发者熟悉LSE的Python环境,或者开发非常简易的插件而使用。

Python 多文件插件开发方法

  • 对于大型、正式的Python插件,强烈建议使用此方法进行开发。多文件插件支持所有完整的Python特性。
  • LSE 使用pyproject.toml 项目文件进行元数据储存(类似 Node.Js 的package.json)。此项目文件推荐使用支持现代项目特性的 PDM 包管理器(pdm-project/pdm)自动生成,以便于进行插件项目的创建和维护。
下面是具体的插件开发流程:
  1. 首先,安装Python 3.12.8

  2. 在终端中执行pip install --user pdm命令安装 pdm 包管理工具

  3. 创建一个新的目录进行插件开发。在此目录启动终端执行pdm init命令。根据pdm工具的提示创建新项目,填写项目的相关信息。

    • 如果需要安装依赖包,在项目目录执行pdm add <依赖包名>即可

    • 所有的项目元数据和依赖数据都会被自动储存在pyproject.toml 中,无需手动编写。你也可以打开此文件修改版本号、描述等元数据信息

    • 除了使用pdm add命令之外,你也可以直接把项目依赖手动写在requirements.txt当中。在安装插件时,pyproject.tomlrequirements.txt中描述的依赖都将被处理并自动安装。

  4. 接下来创建__init__.py文件,并在其中编写插件代码。加载插件时,Python解释器会首先读取并执行这个文件。

  5. 如果有需要,可以创建多个源码文件,并且通过 import 来引入他们。合理的对源码进行分文件拆分编写有助于提高项目结构的清晰度,方便后续对插件进行进一步的维护和扩展。

Python 多文件插件打包 & 部署方法

  • 在插件编写完成之后,请将 pyproject.toml以及所有插件源码打包为一个zip压缩包,并将文件名后缀修改为 .llplugin
  • __pycache____pypackages__ 等目录请勿打包在压缩包之中
  • .llplugin 文件作为插件分发,安装插件时直接将此文件放置到 plugins 目录即可
  • 在开服时,脚本引擎会自动识别 .llplugin 文件,将其解压到plugins/python/插件名目录,并在目录中自动执行 pip install 安装依赖包,整个过程无需人工干预

Python插件开发的已知问题

平心而论,CPython的代码质量和维护状况有些堪忧。下面给出了一些开发Python插件时需要注意的地方,其中有不少都是由CPython本身的Bug引起的:

  1. 暂时不要使用threading asyncio等特性
    • LSE使用CPython的子解释器作为引擎调度的核心单位,然而CPython自身长期以来对子解释器机制的支持并不完善,存在众多Bug,令人一言难尽。目前上面这些机制所需要使用到的GIL api内部并没有考虑到子解释器的存在,因此一旦使用会发生死锁、崩溃等问题。
    • 如果有并行计算需求的开发者可以暂时使用multiprocess进行多进程并行
    • Python3.12 按计划将对子解释器和GIL相关bug做出针对性的修复,届时LSE将对CPython3.12进行适配,解决此问题。Py3.12预计在2023年10月份左右推出,请耐心等待
  2. 所有Python引擎的sys.stdin被禁用
    • 这是另外一个CPython的bug,具体详见:https://github.com/python/cpython/issues/83526
    • 另外,即使不是上述情况下加载的CPython引擎,也会出现抢夺stdin的问题,导致部分使用了输入重定向机制的工具失效
    • 因此我们打patch禁用了所有Python引擎的sys.stdin,等待后续版本CPython将此系列bug彻底解决之后,再视情况恢复