Skip to content

Refactor recipes / 重构配方结算并支持催化剂与来源筛选(1.21.11) - #138

Merged
PigeonNian merged 1 commit into
dev/1.21.11from
codex/1.21.11-recipe
Oct 3, 2026
Merged

PigeonNian merged 1 commit into
dev/1.21.11from
codex/1.21.11-recipe

Conversation

@WhereisFff

Copy link
Copy Markdown
Contributor

No description provided.

- 统一资源缓存与回滚并修复重复结算和组件串用
- 支持催化剂预留、输入输出来源筛选和提交完成事件
- 保留旧配方接口并加入二十项游戏内回归验证
@Gugle2308

Copy link
Copy Markdown

🌿 Roseau API Breaking Change Report

Module Status Breaking Changes
codec ⚪ Skipped —
collision ⚪ Skipped —
cube ⚪ Skipped —
config ⚪ Skipped —
integration ⚪ Skipped —
moveable-entity-block ⚪ Skipped —
network ⚪ Skipped —
rendering ⚪ Skipped —
space-select ⚪ Skipped —
font ⚪ Skipped —
util ⚪ Skipped —
explosion ⚪ Skipped —
rpc ⚪ Skipped —
math ⚪ Skipped —
multiblock ⚪ Skipped —
recipe ⚪ Skipped —
registrum ⚪ Skipped —
sync ⚪ Skipped —
wheel ⚪ Skipped —
main ⚪ Skipped —

Full CSVs: see the Artifacts section of this workflow run.

@Gugle2308

Copy link
Copy Markdown

审查完成。以下是审查结论(已核对 head 源码 88a83ec、base 1336bef、本地 git diff 与下载 diff 逐行一致、CI 42/42 绿)。


代码审查摘要 — PR #138

操作: opened(base dev/1.21.11 @1336bef8 → head 88a83ec4,未合并)
范围: 26 文件(24 Java / 8 新增 / 0 删除),+1552 / −172
CI: 42/42 checks 全绿,但矩阵不含 module.test(见 💡13)

这个 PR 把世界内配方的「结算」重构成 planning → commit 两阶段:InWorldRecipeContext 新增 assertPlanning/invalidate/beginBatch/afterCommit/batchId,资源 acceptor 按 phase(0/100/200)+key 排序提交、成功后执行 deferred effects;ItemCache 引入一次性提交(committed)、差量写回(dirty)、元素按身份复用、ItemCacheSlotProvider 物理槽位解析、逐区扫描(修 bbox 间隙误判)、按 (item, sourceFilter) 区分输出视图、可取消的 SelectOutput / 可拦截的 SpawnItemEntity;HasItemIngredient 新增催化剂(consume=false)与 count>=1 校验;匹配器新增容量预检以避免指数回溯;外加 20 个 GameTest。

🔴 关键

1. 既有断言与本次行为变更直接冲突,-PportSmoke 移植验证会失败

module.test/src/main/java/dev/anvilcraft/lib/v2/test/port/PortVerification.java:173(不在本 PR diff 内,git diff rvbase12111 rvhead138 -- .../PortVerification.java 为空):

check(previous.clears == 0 && successful.clears == 1, "成功匹配仅清理新增谓词");
// CountingPredicate.clearStack(ctx) { clears++; }
  • base:InWorldRecipe.matches 成功分支末尾有 for (int i = initialStackSize; ...) stack.get(i).clearStack(context);(base InWorldRecipe.java:249)。
  • head:该循环被删除,clearStack 移到了 assemble(head InWorldRecipe.java:290)。这个移动本身是必要且正确的——新的 receipt 机制要求 accept() 之前 view/element 的操作栈仍然存活。
  • 后果:只调用 matches() 的该断言,successful.clears 现在恒为 0 → AssertionError(PortVerification.check 直接 throw)→ PortSmokeClient 的 PortVerification.run() 失败 → module.test/build.gradle 的 doLast 因 port-smoke-result.txt 不是 PASS 抛 GradleException。
  • 由于 CI 不跑 -PportSmoke、也不编译 module.test,这个回归不会在 CI 暴露,只会在维护者按 PORTING_1_21_1_TO_1_21_11.md 做移植验证时炸。
  • 建议:同步更新该断言(如断言 assemble 之后 clears == 1、或在 recipeRollback 里补一次 assemble),并顺手确认是否还有其他依赖「matches 内清理」的隐式约定。

⚠️ 警告

2. ItemEntityCacheElement.sync() 不再无条件重置 age —— 可能是行为回归

head 新增 if (!this.dirty) return;,而 base 是无条件 setAge(0) + setItem(simulate)。dirty 只在 shrink/grow/restoreReservation 时置位,因此:仅做存在性检查的谓词(HasItem)以及未被消耗/未被改动的元素,都不再被刷新 age。此前「每次结算都把命中范围的物品实体 age 归零」是机器里物品不消失的常见依赖。请确认是否有意为之;若需要保鲜,应对命中元素显式 setAge(0) 并补一条测试。
(正向:setItem(this.simulate.copy()) 修掉了 base 里元素与实体共享同一 ItemStack 实例的问题。)

3. HasItemBase.applyFunctions 里的 value 是死赋值,且入参语义被削弱

ItemStack value = stack.copy();
for (IPredicateFunction<?> function : this.functions) {
    value = ((IPredicateFunction<ItemStack>) function).apply(context, value);   // 结果从未被使用
}

value 从未被读;同时入参由元素自身的栈改为 copy()——若某个 IPredicateFunction 依赖原地修改入参(当前唯一实现 SaveComponentToTag 只写 TagCache,故暂无实际影响),改动会被静默丢弃。建议二选一:提供能回写的通道(把结果写回 holder),或去掉拷贝并注释「函数只允许产生副作用」,顺便清掉那条 TODO: This cast is unsafe。

4. accept() 的幂等契约与失败路径不自洽

accept() 在 effects 之前就置 completed = true;若某个 afterCommit 副作用抛异常 → invalidate() → 此后任何 accept() 抛 IllegalStateException(而 commitPhasesAreStable 固化的是「二次 commit 为 no-op」)。此时资源已提交、副作用部分执行、上下文残留 invalid。建议 effects 逐项 try/catch(记录而非传播),或明确文档化「副作用失败 = 上下文失效」。

5. count <= 0 校验在构造器、而 codec 未校验 —— 旧数据包报错方式不友好

HasItemIngredient.java:57 抛 IllegalArgumentException,但 ItemIngredientPredicate.CODEC 仍是 Codec.INT.optionalFieldOf("count", 1)。含 "count": 0 或负数的既有数据包现在会在配方解析期抛 IAE(被原版吞掉、静默丢配方)。与同一 PR 对 max_efficiency 的处理不对称,建议改为 Codec.intRange(1, Integer.MAX_VALUE).optionalFieldOf("count", 1),把错误前移到 DataResult 阶段。

💡 建议

  1. ItemCache.range 已成死状态:inRange 改看 scannedRanges 后,this.range 只在 grow 内自增自读(除 isEmpty() 判断外无任何读者)→ 可连 grow 里的 if (!this.range.isEmpty()) ... else ... 分支一起删掉。
  2. scannedRanges 是无上限 ArrayList,inRange 每次 O(n):同一 (pos,range) 重复查询因 contains 命中不会追加,但部分重叠的查询会持续追加(生命周期=单次 plan,风险低)。建议加注释或做合并。
  3. checkItemCapacity 的不变式建议写明:其健全性依赖「谓词候选元素集合两两不相交 ⇒ 贪心 compatible 与穷举 backtrack 等价」。我逐条核对过结论成立(不相交 ⇒ 各谓词的 test 只受自身元素影响),但这层推理不写注释极易被后续「顺手让 compatible() 也走快速路径」改坏。
  4. SelectOutput 的 veto 被缓存:事件只在视图首次创建(cache miss)时投递一次,即每个 (item, sourceFilter, range) 每 plan 只询问一次、后续 batch 不再询问——这是被新测试固化的设计,建议写进 javadoc。
  5. SaveComponentToTag 由 computeIfAbsent(先写者胜)改为 putTag(后写者胜),导致 TagCache.computeIfAbsent(Identifier, Function) 现已无调用方(可删);同 batch 内多个函数写同一 path 的语义变化建议在 PR 描述里点明。
  6. 热路径分配:getInput 不再在收集阶段按物品谓词过滤元素,而是在 sourceFilter 闭包内对每次 getCount()/shrink()/apply() 逐个 element.getStack()(每次一次 simulate.copy())。语义我核对一致(availableElements() 用同一 filter、capacity 模型与 getCount() 同源),但分配量上升,建议压测确认。
  7. ItemEntityEventListener 缺少异常降级:trigger()/accept() 的异常(如 sync() 的 could not synchronize slot、提交期副作用失败)会直接冒泡进实体 tick 事件 → 服务端异常/崩溃。本 PR 的 invalidate() 已保证不会重复提交,正好适合在这里 try/catch + 日志降级。
  8. 新增测试未接入 CI:.github/workflows 中无 -PrecipeTests / -PportSmoke,且 42 个 check 里没有 test 模块 → 本 PR 的 20 个 GameTest 只被编译、不会执行。建议把 :anvillib-test-neoforge-1.21.11:runGameTestServer -PrecipeTests 加进矩阵,否则这批测试形同摆设(🔴1 也正因此没被拦住)。
  9. PR 描述为空,建议补上:consume 语义与默认值、commit phase 顺序(0/100/200)、TagCache 覆盖写、ItemEntity age/discard 变化、以及 ItemCache 的「每 plan 一次性提交」契约(accept() 只能调用一次,ensureOpen 会拒绝后续 getInput/getOutput)。

🟢 看起来不错

  • receipt 幂等链正确:ItemConsumption.restore() + releasedReservations 双保险,催化剂提前归还后 rollbackShrink() 正确返回 0,不会双重归还/双重计数。
  • receipt 与谓词的配对正确:receipt 是 per-view FIFO,而 view 的 push 顺序恰等于成功匹配后 ctx.stack 的顺序(回溯中被 pop 的 receipt 已由 rollbackShrink 摘除),故 assemble 按栈序取 FIFO 成立;即便两个值相等的谓词共享同一个 view(TestIgnoreCountPredicate 是值相等的),配对依然正确。我按 catalystReservesWithoutConsuming / failedAssemblyRestoresReservations 两条路径手推验证过。
  • incompatible 的三态逻辑健全:IMPOSSIBLE 的判据(单谓词 count > 该谓词候选总容量;同一候选集合组的 demand > 组容量)确为必要不可满足;候选集有交集的组一律 independent=false 回退 BACKTRACK,不会误判成不可满足。
  • endCache 用 IdentityHashMap 去重元素 → 输入/输出视图重叠时物理槽位只写一次;committed + dirty 使重复 finalize 幂等(测试覆盖)。
  • ItemCacheSlotProvider 自映射短路 + 环检测(visited)正确;handlerElements 保证同 (handler, slot) 只建一个元素,elementRanges 的多位置匹配修正了此前「只认首次位置」的问题。
  • 序列化向后兼容:consume 默认 true(JSON + stream 双格式 roundtrip 已测),max_efficiency 的 intRange 不影响缺省值。
  • 其它明确改进:entity.discard() 取代置空栈、SpawnItemEntity 可拦截、SpawnOperation 不再被就地 setCount(1) 破坏、beginBatch 只清 TagCache 而保留物品预留、资源提交的阶段化排序(item → 自定义 → block)。
  • 测试覆盖相当扎实(回滚、催化剂、组件跟随被消耗项、来源筛选计数、分阶段提交、幂等提交…共 20 例),命名与断言信息清晰。

🧪 测试建议

被测目标 建议场景 优先级
PortVerification.recipeRollback 更新断言以匹配「clearStack 移至 assemble」 🔴
ItemEntityCacheElement.sync() 未 dirty 的元素是否仍需 setAge(0)(保鲜语义) 🔴
HasItemIngredient codec "count": 0 / 负数数据包的加载错误信息 🟡
InWorldRecipeContext.accept() afterCommit 副作用抛异常后的可重入行为 🟡
ItemCache.grow/inRange 大量部分重叠查询下的 scannedRanges 规模与扫描开销 🟢

📋 标题建议

gh auth status 显示 token 已失效(未认证),本次未自动修改标题。当前英文部分 "Refactor recipes" 过宽,未体现本次核心(结算阶段化 + 催化剂 + 来源筛选),建议改为:

Refactor in-world recipe settlement with catalysts and source filtering 重构配方结算并支持催化剂与来源筛选

结论: REQUEST_CHANGES — 重构方向与实现质量都不错(receipt 幂等、阶段化提交、差量写回、来源筛选的语义我都逐条核对过,成立),但需先处理 🔴1(与既有移植验证断言冲突,且 CI 不会发现)并确认 ⚠️2/⚠️5 的行为变化,再合并。


由 Hermes Agent 审查

@Gugle2308

Copy link
Copy Markdown

💾 Self-improvement review: Patched SKILL.md in skill 'recipe-settlement-refactor-review' (1 replacement). · Patched references/anvillib-v2-recipe-settlement-review.md in skill 'recipe-settlement-refactor-review' (1 replacement). · Patched references/shell-only-diff-navigation.md in skill 'github-webhook-pr-review' (1 replacement).

@PigeonNian
PigeonNian merged commit 56505d9 into dev/1.21.11 Oct 3, 2026
42 checks passed
@Gugle2308

Copy link
Copy Markdown

PR #138 状态为 closed,按 webhook 规则(closed/labeled → 停止)不进行审查、不发布评论。

(无需进一步操作。)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants