Skip to content

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

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

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

Conversation

@WhereisFff

Copy link
Copy Markdown
Contributor

No description provided.

- 修复共享槽位重复结算、回滚顺序和批次组件串用
- 支持催化剂预留、来源筛选与提交完成事件
- 校验旧物品处理器同步结果并加入二十一项游戏回归
@Gugle2308

Copy link
Copy Markdown

⚠️ Dangerous command requires approval:

cd /tmp; grep -n "^diff --git" pr139.diff | cut -d: -f1 > h.txt; total=$(wc -l < pr139.diff); prev=""; n=0; while read l; do if [ -n "$prev" ]; then n=$((n+1)); h=$(sed -n "${prev},$((l-1))p" pr139.di...

Reason: Security scan — [MEDIUM] Variation selector characters detected: Content contains Unicode variation selectors (VS1-256). These are commonly used in emoji sequences but may indicate steganographic encoding or obfuscation

Reply /approve to execute, /approve session to approve this pattern for the session, /approve always to approve permanently, or /deny to cancel.

@Gugle2308

Copy link
Copy Markdown

代码审查摘要 — PR #139

标题: Refactor recipes / 重构配方结算并支持催化剂与来源筛选(1.21.1)
作者: WhereisFff | 分支: codex/1.21.1-recipe → dev/1.21.1
状态: open(data-status="pullOpened",未合并)| 提交: 1 commit | base c35c860e / head ca23c275
PR 描述: 空("body": null)
范围: 29 文件(26 Java、2 Gradle、1 二进制 NBT)/+1682 −176 /92 hunks /10 新增文件 /0 删除

离线取证方式: curl patch-diff.githubusercontent.com(2718 行,29 个 diff --git 头与 API changed_files=29 一致,+/− 行数复算与 additions/deletions 精确吻合,无截断、无 ghost 文件——唯一无 hunk 的是新增的 recipe_empty.nbt 二进制);跨引用用 git show c35c860e:<path> 读 PR 真实 base 树(本地 clone HEAD 与 base 不同源,未直接采信)。


✅ 关键前置确认(已逐项落地验证)

检查项 结论
TagCache.putTag() / public tags 是否已存在于 base(diff 未改 TagCache 却新增调用) ✅ 存在(base cache/TagCache.java),SaveComponentToTag、beginBatch() 编译无虞
AnvilLibTest.MOD_ID / AnvilLibTest.of() / LibRecipeTriggers.ITEM_INTO_BLOCK / IItemHandlerCache ✅ 均存在,新测试引用有效
ItemIngredientPredicate.CODEC 的 count 默认值 ✅ optionalFieldOf("count", 1) ⇒ 新增的 count <= 0 抛错不会打到历史数据
新结构夹具 recipe_empty.nbt ✅ 解 gzip 后 NBT:size = [22, 12, 8],palette 仅 minecraft:air ⇒ 覆盖测试用到的 (1..5, 2, 3) 坐标;无 DataVersion(按当前版本加载,可接受)
module.test 在 -PrecipeTests 下移除 anvilcraft 依赖 ✅ module.test/src 内零 dev.dubhe.anvilcraft 源码引用,neoforge.mods.toml 也未声明 anvilcraft 依赖 ⇒ 不会因缺模组启动失败
HasItemIngredient 新编解码器字段顺序 ✅ offset, range, item, functions, consume 与 5 参构造器一一对应;JSON 侧 consume 有默认 true,旧数据包兼容

🔴 关键问题

未发现必须修复的正确性缺陷。 逐条推演过的高风险点均自洽:

  • matched → reserve → rollback 的栈序:视图层 consumedOperations(FIFO,领取即 pollFirst)与元素层 shrinkSimulateStack(LIFO,pop)在 normal / 失败回滚 / 多 batch(maxEfficiency 循环)三条路径下都能一一对账——rollbackShrink() 用 op 身份从 deque 中删除,clearConsumedItems()/restoreConsumedItems() 与 ItemConsumption.restored + releasedReservations 双重去重,不会重复归还。
  • ① matches 内的 rollback 也在 try 里(if (!matches) rollbackPredicates(...) 在 try 块内)⇒ 回滚自身抛错时会走 rollbackAfterFailure + invalidate(),不会留下"半回滚但仍可提交"的上下文。
  • ShapelessMatcher.checkItemCapacity 的剪枝是健全的:IMPOSSIBLE 的三条判据(单谓词容量、总需求 vs 总容量、同候选集分组需求)都是可行性必要条件,不会误杀可解集合;LINEAR 仅在候选元素集两两不重叠时启用(independent),此时贪心 compatible() 与回溯等价,且 capacities 用 IdentityHashMap、demands 的键是 identity-Set(元素相等性基于 @EqualsAndHashCode 的 final 字段 / entity 身份,hash 稳定)⇒ 分组语义正确。
  • Range 别名陷阱已被规避:grow() 里 this.range = Range.of(pos, range) 而非直接复用 newRange(后者已进 scannedRanges),否则后续 this.range.grow() 会原地改写已记录的扫描范围、造成 inRange 误判漏扫。disjointScansDiscoverGap 正好钉住这一点。
  • scannedRanges 语义收紧(请求范围必须整体落在某一次扫描内才算已覆盖)只会多扫、不会漏扫(实体元素经 computeIfAbsent 去重,重复扫描不产生重复元素)。

⚠️ 警告(非阻塞,但建议合并前处理)

  1. 这是一次库级行为/契约破坏性变更,但 PR 没有任何描述(body 为 null)。 新增的 fail-fast 语义会硬崩调用方,且 AnvilLib 是经 jarJar 进 AnvilCraft 的基础库:
    • ItemCache.endCache() 后,getInput/getOutput/shrink/grow 一律抛 IllegalStateException(ensureOpen());
    • ctx.accept() 后(completed/invalid/committing),matches/assemble/putAcceptor/afterCommit/beginBatch 一律抛 IllegalStateException(assertPlanning());
    • 数据侧新校验:max_efficiency 改为 Codec.intRange(1, MAX)、HasItemIngredient 要求 count > 0、consume=false 要求 input 支持凭据(否则 UnsupportedOperationException)。
      旧行为对"提交后再查询/再提交"是静默容忍(且会重复应用,见下条),现在改成抛错。请补 PR 描述 + 变更说明(含下游迁移指引:BE 每 tick 新建 context、提交后不得再查询同一 cache)。
  2. 失败传播路径变"响",且中途失败会留下已部分写入的世界状态。 endCache() 在同步前就置 committed = true,逐元素 sync();ItemHandlerCacheElement 新增的 extractChecked/insertChecked/isItemValid+slotLimit 校验会抛 IllegalStateException,异常经 DEFAULT_ACCEPTOR → ctx.accept() → trigger() → BE tick(ItemEntityEventListener:22-23)一路向上传播——即服务器 tick 崩溃,且前面已 sync 过的槽位无法回滚、也无法重试。这换来的是"不再静默复制物品"(正确方向),但请确认这是期望契约:对 isItemValid 严格或 extractItem 每次只吐 1 个的(常见于某些 mod 的 handler)会从"静默错误"变成"服务器崩"。建议至少在库文档/异常信息里写明失败契约,或对"无法精确转移"提供降级(如溢出落地/日志)而非硬崩。
  3. 21 个回归测试实际上不会被任何 CI 执行。 generateTests() 由 Boolean.getBoolean("anvillib.recipeTests") 门控,只有新的 recipeGameTestServer run 设置了该属性,而 .github/workflows/* 中没有任何 -PrecipeTests / runRecipeGameTestServer 任务(build_and_test.yml 只跑 :module:build)。即:本 PR 的安全网目前是"手跑专属",既无法防回归,也意味着新 gradle 分支与 -PrecipeTests 编译路径从未被验证过。建议加一个 CI job(顺带验证夹具与依赖裁剪分支)。
  4. 注解体系混用/迁移不完整。 module.recipe 的 package-info 仍是 com.mojang 的 @ParametersAreNonnullByDefault(不是 jspecify @NullMarked),IRecipePredicate 等仍用 javax.annotation.Nullable,而本 PR 把 ICacheElement/ICacheInputOutputImpl/AbstractCacheElement/ItemHandlerCacheElement 换成了 org.jspecify.annotations.Nullable ⇒ 在这条分支上 jspecify 注解是失效的(纯装饰),且 module.recipe/build.gradle 用 compileOnlyApi 把 jspecify 暴露给了所有消费者(module.renderer 用的是 compileOnly)。要么补齐(补 @NullMarked + 清掉剩余 javax 用法),要么保持本分支一致的 javax 风格;AbstractCacheElement 里 import org.jspecify... 还被追加到了 java.util.* 之后(本仓库无 checkstyle/spotless,不阻断 CI,仅风格)。

💡 建议

  • 多来源消费时组件转发语义未定义:HasItemIngredient.accept 只对 getConsumedItems()("最旧未领取"凭据)逐条 applyFunctions。若一次预留跨多个元素(如 1 个槽 1 个 + 另一槽 1 个),SaveComponentToTag 会按视图元素顺序被调用多次,配合新增的"组件缺失即 tags.remove(path)"逻辑,混合批次(一个有 CUSTOM_NAME、一个没有)会变成后写者决定、甚至丢掉组件。测试只覆盖了单元素消费。建议在 javadoc 明确"组件仅取自单一消费源",或显式定义聚合规则。
  • HasItemIngredient.Type 覆写的 CODEC/STREAM_CODEC 与父类 AbstractType 的 codec 重复,父类那份(含 create(4 参))已成为"构造时仍会实例化但永不使用"的死代码(父类构造函数里还会 eager 建一个 StreamCodec)。可考虑把 consume 上提到基类,或让子类只覆写 create。
  • ICacheElement.consume 默认实现、ICacheInput.restoreConsumedItems 默认抛错、supportsConsumptionReceipts()——"预留不可归还"这一契约被三处分别表达,建议统一到 ICacheInput 的接口文档里,方便 addon 作者一眼看懂。
  • 覆盖面补强(当前测试很扎实,仅列缺口):① 预留跨多元素的组件转发;② endCache() 中途某元素抛错后的部分提交状态(现有 rejectsUnsupportedTransfers 只覆盖首个元素即失败);③ restoreStack 遇到"元素物品类型已被替换成另一种物品"时的回滚(新抛 Cannot restore a reservation into a different item);④ -PrecipeTests 下 runRecipeGameTestServer 的端到端可跑通。
  • nits:TestItemHandler.java 是全部新增文件里唯一缺 EOF 换行的(\ No newline at end of file);OutputKey.hashCode() 只取 source.hashCode()(当前 equals(key, range) 路径不依赖 hash,但若将来放进哈希结构需把 item 一并混入)。

🟢 看起来不错

  • 物理槽位去重是真正的正确性修复:handlerElements/entityElements + elementRanges 让"同一 inventory 被两个位置暴露"不再被算两份,endCache 又用 identity-set 保证每个元素只 sync() 一次(outputCallbackRunsOnce 钉住)。
  • 幂等化修掉了静默重复:committed 标志、isInLevel 置位时机、dirty 门控、stack.copyWithCount(1) 替代原地 setCount(1)、以及 spawn 前的 !isRemoved() && !getItem().isEmpty() 守卫——共同消除了基类里"重复 endCache 会重复落物品/重复 addFreshEntity"的隐患(commitIsIdempotent、merge... 系列测试覆盖)。
  • computeByIdentity 替换 "item_cache_input/%s".formatted(hashCode()):从"哈希碰撞会串用别的谓词视图"变成按实例身份缓存,正确且更快;配套的 batchPreservesResources 断言等值键互不干扰。
  • 不可信 handler 的验证(simulate→actual 双检 + 数量/组件精确校验)把"静默吞物品"变成显式失败。
  • 异常安全:matches/assemble 的 try/catch、多异常 addSuppressed 聚合、pop() 的 finally 出栈、rollbackAfterFailure 的 invalidate(),加上 afterCommit 延迟副作用(ProduceExplosion 改为提交后爆炸,避免先炸掉方块再搬物品)与按 (order, key) 排序的提交阶段,整体提交语义清晰了很多。
  • 催化剂实现(consume=false + 预留凭据 + 归还去重)与 SaveComponentToTag 改为"仅消费项 + 每 batch 清 tag + 组件缺失即移除",方向正确且被 21 个 game test 覆盖;ShapelessMatcher 用容量剪枝把剪枝为不可解的排列直接判定,去掉每次 new ArrayList<>(predicates) 的 O(n!) 拷贝。
  • 测试端设计也到位:@GameTestGenerator 门控、GuardedHandler 故意"说谎"、DrainingHandler 模拟回调搬物品、事件监听按 cache 身份登记并在 finally 移除。

📋 标题声称核对(无 PR 描述,故按标题逐项核对)

声称 状态 对应实现
重构配方结算(结算/提交) ✅ InWorldRecipeContext.accept() 阶段排序 + 幂等 + invalidate();InWorldRecipe 的异常安全回滚;ItemCache.endCache() 去重同步
支持催化剂 ✅ HasItemIngredient.consume(codec/stream/build 三处)+ 预留凭据归还 + catalystReservesWithoutConsuming / 两个 *HandlerCatalyst 测试
支持来源筛选 ✅ ItemCache.getInput/getOutput 的 sourceFilter 重载、ICacheElement.getSource()/getSourceSlot()/isGeneratedOutput()、ItemCacheEvent.SelectOutput(可取消)+ sourceFilterAccountsExactly / recipe_output_filters_separate_keys
配方无序匹配性能/正确性 ✅(标题未提,但属实质变更) ShapelessMatcher 容量剪枝 + LINEAR 快路径 + 回溯去掉拷贝

🏷️ 标题建议

gh auth status 显示 token 已失效(The token in .../hosts.yml is invalid),按流程未执行 gh pr edit(也不去 source .env)。建议标题更贴合实际范围:

Refactor in-world recipe settlement: catalysts, source filtering, atomic commits 重构世界内配方结算:催化剂保留、来源筛选与原子提交 (1.21.1)

结论:COMMENT(倾向 APPROVE,建议补齐文档 + CI 后再合并) — 未发现阻断性正确性缺陷,重构方向与实现质量明显高于基线(多条修复有测试钉住);主要诉求是把新增的 fail-fast/数据校验/API 破坏性变更写进 PR 描述与发布说明,并把 21 个回归测试真正接进 CI。


由 Hermes Agent 审查

@PigeonNian
PigeonNian merged commit 224c6f8 into dev/1.21.1 Oct 3, 2026
42 checks passed
@Gugle2308

Copy link
Copy Markdown

❌ Non-retryable error (HTTP 400): HTTP 400: You have insufficient credits to make this request. Please purchase more credits to continue using the service.

@Gugle2308

Copy link
Copy Markdown

HTTP 400: You have insufficient credits to make this request. Please purchase more credits to continue using the service.

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