这一页不是玩法说明,是底下那层:插件之间怎么说话、数据存在哪、为什么我们宁可自己写一套。
不看也不影响你玩。想知道「服务器出问题的时候我们在改什么」的话,这里有答案。
一句话地图
第 0 层 引擎绑定 对 BDS 的安全封装
第 1 层 通用基建 存储层(SQLite) 网页服务
第 2 层 三个管理器 权限 世界 地皮
第 3 层 玩法与工具 RCC 核心 建筑工具 起床战争
箭头只能从上往下画。 第 3 层的玩法插件不允许直接碰第 2 层的内部实现,只能经过各管理器对外的 SDK。
这条约束不是风格问题。它换来的是:一个只想列世界的插件,不会被迫拖进整套权限类型;改权限的内部结构,不用重编玩法插件。
三个管理器每个都是三件套:
| 部分 | 是什么 |
|---|---|
| SDK | 契约类型、服务名。别的插件只依赖这一个 |
| 内核 | 纯逻辑,不依赖游戏引擎,能在没有服务器的机器上跑测试 |
| 胶水 | 把两边接起来,唯一必须在真服务器上验的一层 |
「内核不依赖引擎」是整套架构里最值钱的一条。权限判定、地皮几何、继承关系、准入规则,全部能离线测——不用开一台 BDS 就能验证「先给整包再挖洞」这种规则算得对不对。
插件之间怎么说话
两条通道。
通用通道是「名字 + JSON 进,JSON 出」,任何语言写的插件都能用。
快车道只服务一个特例:两边都是同一次编译出来的 Rust 插件。这时候可以直接递函数表过去,跳过序列化。
收益不在「快」
一次通用调用一两微秒,命令门禁那个频率下根本不是瓶颈。真正的收益是另外两件事:
批量。「这个玩家对这 40 个节点分别有没有权限」,走 JSON 是 40 次往返、或者一个 40 字段的载荷;走快车道是一次调用,而且上下文只算一次。
类型。 权限判定的结果是个三态——允许、拒绝、没规则。走快车道它原样过去;走 JSON 它得被拆成字段名再拼回来。
一个不会被报上来的 bug
假设 JSON 那头字段名从
allowed打成了allow。读的一侧取不到值,退回默认的false。于是表现是:「这个人没有权限」。
没人会觉得这是 bug。玩家以为自己确实没开通,管理员看配置又看不出问题,最后大概率归档成「他记错了」。类型直接过去就没有这一整类问题。
指纹,不是版本号
Rust 没有稳定的二进制接口。所以快车道比的不是版本号,而是一整串指纹:编译器完整版本、目标平台、编译档位、优化等级、车道名、契约版本,加上函数表的内存布局。
任何一项对不上,一个指针都不会递出去,自动降回 JSON 通道,功能一条不少。
失败模式是「慢」,不是崩。这是这条快车道敢存在的全部理由。
有个真实的坑值得记一笔:早先几个插件分属不同的编译配置,优化等级一个写 2 一个写 3。指纹里含优化等级,于是跨插件调用静默降级成了 JSON——功能完全正常,所以谁也没发现,只是每次调用多一次序列化。
现在整棵树在一个工作区里一次编完,这个坑从根上没了。
数据存在哪
统一走一个共用的 SQLite 层,SQLite 本身编进二进制,服主不需要自己装。
地皮归属、权限组、世界配置、在线时长,都在那里面。少数不适合关系型存储的(比如地图瓦片)单独落文件。
网页管理界面
权限插件带一个网页界面,用的是一个零依赖的小 HTTP 服务,前端直接内联在二进制里——没有 node_modules,没有构建步骤,没有第三方前端资产。
默认关着。开启需要同时满足两件事:显式打开,并且填一个非空 token。token 是空的就拒绝启动,日志里写明原因。默认只监听回环地址,绑到别的地址上会留一条警告。
三条自我约束
这三条是写给未来接手的人的,不是给玩家看的,但它们直接影响你遇到 bug 之后多久能修好:
单个文件不超过 400 行。 三百多个源文件,最长那个 394 行。
代码里没有注释。 所有「为什么这么写」搬到单独的说明文件里,路径和源文件一一对应。理由是注释会和代码一起被改乱,而独立的说明文件改起来必须是有意识的动作。
一条命令编完整棵树。 十几个包一次编完,不用切目录,也就不会再出现「各自编各自的、指纹对不上」那种事。
这些约束由脚本机器检查,不靠自觉。
为什么不用现成的插件
三条,按重要性排:
出了问题只能等。 现成插件的作者不欠我们什么。等不到就只能把功能去掉,而去掉的那个功能是玩家已经在用的。
我们的需求确实特殊。 这是一台以红石为主的服务器,对 tick、对区块、对实体的要求比一般服务器苛刻得多。通用插件的默认取舍常常不适用。
提出问题的人和改代码的人在同一个群里。 这是最实际的一条。你在群里说一句「这个开关没生效」,当天就能有人去看那段代码。
代价也很明确:所有的 bug 都是我们自己的 bug,没有人可以怪。
常见问题
服务器崩了是不是这套东西的问题?
有可能,也可能是引擎本身或者某个红石电路。崩溃日志我们会看,能定位到具体插件的话会在群里说明。
插件开源吗?
目前没有公开仓库。想了解具体某处怎么实现的可以在群里问。
为什么用 Rust 写基岩版插件?
主要是为了「越界了会编译不过」这件事。方块坐标、维度号、权限判定结果这些东西在类型上是分开的,写错了在编译阶段就被挡住,而不是在服务器上表现成一个奇怪的现象。
一个插件挂了会不会连累别的?
设计上不会。插件之间是可选依赖,连不上就走「离线」路径——比如权限插件不在,别的插件不会报错,只是所有判定退回各自的默认值。这条路径是正常路径,不是错误处理。