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