Skip to content

Architecture

shinyashen edited this page Sep 17, 2026 · 10 revisions

English | 中文

架构与设计

本页讲清楚三件事:VM 如何接入 AE2(为什么用根节点替换)、VM 内部长什么样(字节码与执行)、正确性如何兜底(守卫体系)。功能清单见功能明细,数学部分见求解器专页。

1. 接入方式:根节点替换

原版(1.21)在 CraftingService.beginCraftingCalculation 层拦截请求并返回自建 ICraftingPlan。1.12 的 CraftingCPUCluster.submitJob 只接受内部 CraftingJob 类型(instanceof 硬检查),拦截层方案行不通。

本移植改用根节点替换:CraftingJobMixin 把作业的根节点换成 VMRootNode——它的 request() 运行 VM,setJob()/getPlan()/dive() 把 VM 计划喂回 AE2 原生作业生命周期(模拟通过、缺料展示、CPU 执行全部复用原版逻辑);VM 失败时回退原生递归树。这样"入口替换、下游全兼容",AE2CT 等依赖显示树的附属不受影响。

键类型相应从 1.21 的 AEKey 换为 IAEItemStack 类型键(equals 语义 = isSameType)。

2. 编译期:样板 → 字节码

每个样板编译为一段线性字节码,要点:

  • 输出与记录:常量池载入输出键,DUP + RecordPattern 绑定样板引用(bundle 键的一半);
  • 输入行遍历:CondensedInputs 逐行编译为"提取库存 → 乘合次数 → 子样板调用 / 缺口上报"的指令序列;
  • 替换槽:可替换输入编译为 FUZZY_SLOT 标记 + 逐变体 EXTRACT 指令,执行期按库存实况在变体间选择;
  • 催化剂 / 耐久:对"输入被产出"的模式做一次性探测(返回量 = Long.MAX_VALUE 即 CATALYST_SEED 种子;有限返回量在 1.12 编译为普通毛计费输入——AE2UEL CPU 无法再消耗磨损回流,见差异与路线);
  • 流体规范化:AE2FC 包形式输入规范化为 drop 形式键,普通物品做大小归一;流体键保留 mB 数量(子样板查询依赖它);
  • 全量预编译:网格重算时新样板立即编译(mixin 挂钩),首次请求不再承担编译成本。

3. 执行期:栈机语义

  • 零递归:指令顺序执行,子样板调用是 CALL 指令而非语言级递归;需求沿显式结构传播;
  • BigInteger:数量运算全部无限精度,x1 / x2 边界、10^9 深链、超长累计都无溢出可能;
  • 快路径:整数路径大量使用 long 运算(0–1023 预分配、2 的幂特判、Math.addExact/multiplyExact),溢出才降级 BigInteger——正常规模请求付出BigInteger 语义、拿到 long 速度;
  • 库存感知切分:每个键"先用库存、缺口再合成";精确槽 / 模糊槽独立切分。

为什么用 BigInteger 栈?(对一个常见疑问的回应)

有观点认为 BigInteger 栈"只是增加内存占用"。这个成本是真实存在的,值得把账算清楚,再说清它换来了什么:

  • 成本的真实大小:栈上 0–1023 的值走预分配缓存(零分配);≥1024 的值每次压栈产生一个约 40 字节的短命对象,进 nursery 一轮 GC 即回收;栈本身是每个 VM 实例 512 个引用(4KB)。所有计划级计数器(used/missing/emitted、样板次数、全部 AE2 库存读写)都是原生 long——BigInteger 只存在于求值栈与代数层。最重夹具(fib32 × 10^9)完整规划的热中位 0.44 ms,装箱不是可测量的瓶颈;
  • 换来的第一件事:溢出是"被看见的",不是"被吞掉的"。定宽整数的回绕是静默的——原生 AE2 的合成树节点做普通 long 乘法(在其目标规模下完全够用),但极端输入下回绕不报错,会产出看似合法的负数/错账。VM 的算术仅在溢出时降级 BigInteger,任何输入要么得到精确计划、要么得到诚实披露,绝不产出看似合法的错误数量;
  • 第二件事:环代数需要精确有理数。换算环守恒校验用分数比值,净放大环解最小不动点要做带除法的消元——这些在 long 上没有安全写法,朴素 long 分数的中间积会在输入输出都很小时也爆掉;
  • 第三件事:超过 long 的请求有明确语义。请求量超过 Long.MAX_VALUE 时按 Long.MAX 分批交付、余量走批次余量通道(batch remainder),天文数字的字节成本以 double 记账——是"正确切分并如实披露",不是未定义行为;
  • 一个公允的让步:1.12 的库存计数本身就是 long(AE2 驱动如此),超出 long 的数量永远不会被"执行"。BigInteger 表示的正是这部分会计精度——把它算准,以便切分、报缺或拒绝,而不是把它当成可执行数量。执行路径(计数器/计划)全程 long,正是这个分工的体现;
  • 考虑过并放弃的替代:纯 long + 饱和算术(环代数仍需精确有理数,分批语义也随之消失)、128 位定点(JDK 无原生支持,除法/分数仍然缺位)、double(2^53 以上不精确,10^15 尺度即失真)。结论:保留现状。

4. JIT Bundle:捕获一次,回放无限次

bundle 是"以 1 次合成为尺度的真实执行轨迹"。此后任意合次数 n 的需求 = 按 n 线性缩放回放,O(1)。三个设计关键:

  1. 复合键 (键, 字节码):样板集变化自动失效,多样板并存;
  2. subChoices 传递记录:bundle 记录捕获时每个直接子调用解析到的样板,合并子 bundle 时传递合并;回放前比对当前解析,不一致强制重捕获——样板热替换的正确性由此保证;
  3. missingSubKeys:含缺 bundle 记录"缺的是哪些键",聚合上报缺口的数据载体;也是库存缺口重试的判定依据。

5. 守卫体系(正确性优先于性能)

守卫 拦截的问题 机制
死环预剪 互为原料的双键死环选中必缺样板 配方交换图 SCC:自环恒剪;无种子且无外部供给才剪;外部供给 = 环外键的生产者输出含环成员(兄弟候选不算、副产物产入算);单候选场景整段跳过;运行时守卫兜底
JIT 复合键 + subChoices 样板增删改后回放过期轨迹 键含字节码指纹 + 回放前比对子调用解析
计划复核(planMatchesStock) 命中缓存的计划对活库存失效 命中后复核 used 覆盖与缺口补齐,不过回慢路径
版本门控(PATTERN_SET_VERSION) 样板集变化后缓存全体过期 mixin 检测网格样板集 identity 指纹 → AtomicLong bump → bundle/计划缓存同清
CALL slot 门控 模糊槽子调用解析出不可制造样板 执行期校验解析结果的样板仍能产出该键;root 排除(root CALL 归 findRootPattern 管)
求解确认门 求解器模型失真引入更差计划 混合分配必须真实重放确认严格更少缺失才采纳
库存缺口重试(shortfallRetryable) 补料后重算回放过期缺口 含缺键在新库存可解 → 真实重执行
环采纳门(物料闭包) 环求解模型失真引入坏计划 双趟结构趟 + 逐成员物料闭包复验,违背即放弃采纳、传播计划原样生效

设计红线:任何优化不得越过守卫。bundle 键必须完整限定;捕获期不得污染沙盒状态(claim 提取计入 delta 账,revert 对称返还);求解器采纳必须有确认门;执行期校验不得作用 root。

6. 环求解器(净放大环的代数折叠)

⚠ 状态(1.2.0 起):整节所述管线由 AE2VMConfig.ringSolverEnabled 门控,当前硬编码 false(默认禁用)。 实机排查发现部分整合包中,合成路径包含环产物的物品下单可能卡死合成 CPU(下游场景,魂充足仍复现,bench 无法复现);关闭时 ringNetBundles 恒空、环后处理全部空转,纯传播计划驱动聚合。环求解器与 E 案代码完整保留在门后,环测试经 @BeforeAll 显式开启继续钉住行为;问题定位后开关将转为正式配置项。

净放大互环(盖亚之魂家族)的排程由独立的环求解器完成,与传播聚合、守卫体系分层:

  1. 检测:解析器图(含 T4 副产物回退——唯一任意槽生产者的键入图)上做 Tarjan SCC;
  2. 双趟求解:趟 1 结构可解性(cover 不含库存,环必须自持闭合——净亏损环无论库存多少都在此发散被拒);趟 2 计库存从下方求物料最小解(最小不动点,不继承传播的取整粒度);
  3. 折叠:整个环折叠为单个毛流量 bundle——产物先插入、原料后提取,环内周转自洽;启动垫资(时序资本)单独披露;
  4. 耦合:跨环耦合恒为单向(缩聚图是 DAG),消费者优先拓扑序求解,折叠环的解算净抽取写回生产环的地板;
  5. 采纳门:逐成员物料闭包复验,违背即放弃采纳、传播计划原样生效。

有界性与设计参数(源码 RingSolver 顶部常量,均有意为之而非随意值):

参数 值 设计理由
MAX_RING_SIZE 64 环成员数上限;超出即放弃折叠、回退传播计划。雅可比每轮 O(n)、seed 探序 O(n²),上限封顶最坏求解成本;真实整合包的环远小于此
MAX_ITERATIONS 64 雅可比迭代轮数上限。净增益环几何收敛(数轮即稳定);净亏损环在该上限内必然发散 → 判为不可折叠。两类环都得到确定性行为
CRAFT_CAP 10^12 单样板合成次数上限;超出视为发散放弃(转 long 前的安全界,防止单键计数失控)
seed 探序 O(n²) 每个成员各作强制起点 × 就绪优先继续——最小启动垫资的确定性近似,方向保守(只可能多报缺,不可能漏报);不追求任意点火顺序下的最优证明

边界划分:净亏损环归损耗垫资模型,换算环归守恒校验,催化剂循环归种子机制——环求解器只做"每轮净增"的环,门控保证各类环各归其位。

7. 性能结构(简述,数据见性能页)

  • 需求传播聚合 O(patterns + edges),共享 DAG 节点单次回放;
  • 计划记忆化:重复请求微秒级直返(活库存复核后);
  • 模糊族两层缓存:sandbox 族列表按键记忆(键集合在请求内稳定)+ VM 合成族(替换组 ∪ NBT 族)按键记忆;
  • 求解器快路径:long[] 级联 + 库存预读,溢出降级 BigInteger。

8. 测试如何驱动设计

全部守卫与修复都由对拍测试驱动:以 Thunderbolt 参考套件(12 族 × 3 库存模式,1.21 概念翻译为真实 1.12 样板形状)为能力面基准,配合专门构造的回归用例。对拍暴露过的真实语义缺口(催化剂种子自满足、精确槽误吞替换组、快照缺族、bundle 过期)全部转化为回归测试钉死。详见测试页。

Clone this wiki locally