Skip to content

Fix cannot search item name in terminal overlay 修复终端浮窗内无法搜索物品名称的问题 - #4688

Merged
PigeonNian merged 3 commits into
Anvil-Dev:dev/1.21/1.6from
QiuShui1012:fix/1.21/1.6
Sep 2, 2026
Merged

Fix cannot search item name in terminal overlay 修复终端浮窗内无法搜索物品名称的问题#4688
PigeonNian merged 3 commits into
Anvil-Dev:dev/1.21/1.6from
QiuShui1012:fix/1.21/1.6

Conversation

@QiuShui1012

Copy link
Copy Markdown
Collaborator

No description provided.

@Gugle2308

Copy link
Copy Markdown
Collaborator

代码审查摘要 — PR #4688

操作: opened (webhook 自动审查)
范围: 2 个文件 (2 Java) / 278 行 diff
分支: fix/1.21/1.6dev/1.21/1.6

🔍 问题背景与修复方案

修复前:terminalReorder 服务端 createOrder普通文本搜索(无 @/# 前缀)一律返回全部条目(不过滤)——该设计本意是「本地化名称匹配由客户端完成」(服务端无客户端语言环境),但 StorageScreenapplySearchFilter 做客户端二次过滤,而浮窗 TerminalRemoteOverlay 没有对应的客户端过滤,只有 StorageScreen 有 → 终端浮窗内输入普通文字(如 iron)搜不出任何东西。本 PR 在浮窗侧补齐了「全量内容同步 + 客户端按显示名/id path 过滤」链路。方案正确,与 StorageScreen.applySearchFilter(行 2980-3001)语义一致。

🔴 关键

  1. TerminalRemoteOverlay.java:598-599 — 同步失败/中途退出时缓存可能被永久丢弃,降级后无法自愈
    失败分支(syncFullPage 行 512-518)只把 order 回退到 baseOrder,但不清空 CONTENTS/COUNTS,也不重置 fullSyncing=false 之外的状态。若失败发生在 syncVisible() 已按"新 baseOrder"裁剪过缓存之后(syncFullContentsorderChanged → trimContentCache 先执行),用户会看到全部槽位空白/闪烁;且下一个 refresh() 周期的 missing 非空会重新触发全量同步,但若存储很大、周期性失败,会反复出现"部分条目消失、20 tick 后又回来"。建议失败分支同时清空内容缓存(与旧逻辑的"排序变化清空缓存"等价),确保下轮从干净状态重建。

  2. syncFullPage 递归没有 tick 间隔,大存储下全量同步可能饿死渲染/输入线程
    syncFullPage 成功后立即同步调用下一页(to < missing.size()),直到全部拉完。每次 RPC.invoke 都是网络往返,但 whenComplete 回调之间无帧间隔——在 几万槽位的存储 + 页面命中空槽位(sync 跳过越界)场景下,可能一次刷新周期内连续发起几十上百个 RPC。建议:每页之间加帧节流(如分多次 refresh 周期),或限制单次 refresh 的全量页数。

  3. syncFullContentsfullSyncing 抢占与 refresh()orderChanged 误判组合可能造成"永远等不到过滤结果"
    场景:用户搜索 ir → 全量同步进行中(fullSyncing=true)→ 用户改搜 iro → 下一次 refresh() 到达,orderChanged(baseOrder 变化,因服务端排序可能不同)→ 走 syncFullContents,但 fullSyncing=true直接 return,不触发任何新同步。要等当前同步完成回调才恢复。如果当前同步被卡住(服务端慢/包丢失)且新搜索词的缓存未包含全部匹配槽位,过滤结果会一直不出现(尽管有周期 refresh 兜底)。建议在 fullSyncing=true 且 orderChanged 时,记录待处理标记或在回调结束时用最新 baseOrder 补一轮(现有 syncedBase.equals(baseOrder) 检查只在成功路径生效,失败路径没有这个补救)。

⚠️ 警告

  1. view != null 守卫移除(StorageServerStub.java:1693)与 PR 主题无关,且改变了失败回滚语义
    该改动位于 transferMaterialExact 的"材料不足回滚"分支。改动后 fromStorage > 0 时无条件 view.insert(...)。从代码看:fromStorage 只在 view != null 的分支内自增(行 1669-1699),view 为 null 时 fromStorage 恒为 0,所以 fromStorage > 0 → view != null 恒成立,移除守卫是安全的(消除了 spotbugs/静态检查的冗余判空)。但这与"修复终端搜索"主题无关,且回滚 insert 前缺少 view != null 会让未来重构更脆弱。建议拆到独立 commit 或至少保留注释说明不变式。另确认:其余 view == null 回滚/放置路径(行 1291/1415/1748)均保留守卫,未受影响。

  2. applyClientFilteridPath 未按 Locale.ROOT 小写(行 550)
    StorageScreen.applySearchFilter(行 2991-2993)完全一致(那边也没对 idPath 做 toLowerCase),但 idPath 来自注册表 key,理论上是小写 ASCII,实际无 bug——仅提示与注释中"与 StorageScreen 一致"的声明准确。

💡 建议

  1. refresh()trimContentCache 现在只裁剪不重建
    旧代码在 orderChangedCONTENTS.clear(); COUNTS.clear() 然后重新同步可见页——每次排序变化都全量重拉。新代码 trimContentCache 保留交集(对排序局部变动友好),但 refreshorderChanged 判断从 next.equals(order) 改为 next.equals(baseOrder)baseOrder 是服务端返回的全量排序,而 order 在搜索态是过滤后子集。这意味着非搜索态下滚动/翻页等不改变服务端排序的操作不会再触发缓存清空(行为等价,正确),但搜索态下 baseOrder 不变而过滤结果因客户端内容变化而变时,syncVisible 只拉可见页、缓存保留——若用户从搜索态切换到空搜索,order 变回 baseOrder 而部分槽位内容未缓存,会短暂显示空白直到 sync 补齐。可接受(最终一致),但建议在搜索态切非搜索态时主动触发一次 syncVisible 或全量补缺。

  2. FULL_SYNC_PAGE = 256 硬编码与 MAX_SYNC_SLOTS 的耦合
    注释说明与服务端 MAX_SYNC_SLOTS 一致。若服务端上限调整,此处需同步改。可考虑提取为共享常量或从服务端 metadata 获取,避免漂移。另外单次 256 槽的 sync 响应可能很大(256 个完整 ItemStack 序列化),普通玩家存储几千槽位时全量同步流量可观——建议对超大存储限制全量同步(例如只同步前 N 页),普通文本搜索在超大存储上可降级为 id path 服务端过滤(id path 是 ASCII 不需要语言环境,服务端本可以按 id path 过滤普通文本——StorageScreen 的过滤同时匹配 name 与 idPath)。

🟢 看起来不错

  • 服务端 terminalReorder 注释清晰说明「普通文本由客户端二次过滤,服务端只处理 @/# 前缀」,设计意图明确。
  • baseOrder/order 分离 + fullSyncing 防重入 + syncedBase 快照比较做最终一致,状态机设计周到。
  • 失败时降级为展示未过滤的 baseOrder 并保留 syncVisible 刷新,比旧版崩溃/空窗更稳健。
  • 空搜索/@/# 前缀搜索路径保持原行为(不走全量同步),无回归风险。

📋 声称验证表

声称 状态 对应文件
修复终端浮窗内无法搜索物品名称 TerminalRemoteOverlay(新增 syncFullContents/syncFullPage/applyClientFilter/isPlainTextSearch/trimContentCache,~150 行)
客户端按本地化名称二次过滤 applyClientFilter 与 StorageScreen.applySearchFilter 一致
服务端只处理 @/# 前缀 StorageServerStub.terminalReorder + createOrder(注释说明)

🧪 测试建议

被测目标 推荐测试场景 优先级
syncFullContents/syncFullPage 大存储(>256 槽)分页正确性、中途失败降级、搜索词变更竞态 🔴
applyClientFilter CJK 名称、idPath 匹配、@/# 前缀不进入、strip/大小写边界 🟡
transferMaterialExact 回滚 材料不足、view 为 null、存储部分已取回滚 🟢

结论: COMMENT — 修复方向与实现正确(补齐了 StorageScreen 有而浮窗缺失的客户端过滤链路),核心状态机设计周到。但有两处值得修复后再合并:失败分支不清空缓存导致降级态可能长期空白(🔴1)、大存储全量同步无节流(🔴2)。另建议将无关的 view != null 移除改动拆分或补注释(⚠️4)。


由 Hermes Agent 审查

@Gugle2308

Copy link
Copy Markdown
Collaborator

💾 Self-improvement review: Patched SKILL.md in skill 'github-webhook-pr-review' (1 replacement).

@QiuShui1012
QiuShui1012 marked this pull request as draft September 2, 2026 10:22
@Gugle2308

Copy link
Copy Markdown
Collaborator

分析全部完成。综合各疑点,这是 converted_to_draft 状态,按 skill 规定只报严重问题、comment 级别。整理审查意见。

PR #4688 修复方向正确:overlay(悬停浮窗)走的是 RPC 远程排序(服务端无客户端语言环境,普通文本搜索无法按本地化名过滤),此前 overlay 只展示服务端原始排序导致中文名搜索失效;GUI 早有客户端二次过滤。修复方案是在客户端拉全量内容缓存后二次过滤。这与此前 #4682/#4680 存储界面重构引入的 RPC 架构一致。

关键发现(⚠️ 严重度分级):

代码审查摘要 — PR #4688

操作: converted_to_draft(未合入,作者转草稿)
范围: 2 个文件 (179+/9-) / 278 行 diff
分支: fix/1.21/1.6dev/1.21/1.6

🔴 关键问题

  1. 潜在无限分页循环风险(搜索词在同步期间被清空/切换)
    syncFullPage 的收尾(/tmp head 分支 TerminalRemoteOverlay_head.java 约 525-533 行):
    if (isHoveringSame(id) && !syncedBase.equals(TerminalRemoteOverlay.baseOrder)) {
        syncFullContents(id, false);   // ← 若 baseOrder 每 20 tick 刷新都不同,会无限递归补页
    }
    refresh() 每 20 tick 无条件重发 reorder,且 baseOrder 在每次响应时无条件更新。若同步期间存储内容持续变动(如自动化产线在跑),每轮补页都可能发现 baseOrder 又变了 → 再次 syncFullContents → 可能无限循环,fullSyncing 虽防并发但不防串行无限重补。建议:补页限制轮数(如最多 1-2 轮),或比较 syncedBase 与当前 baseOrder 的内容集合而非整体相等,仅当新增了缺失槽位才补。
  2. 搜索词中途变化时旧过滤结果可能残留展示
    applyClientFilter 在收尾时用当前 search 过滤,但页面内容 (missing) 是基于搜索触发时缺失的槽位同步的。若同步期间用户把 "iron" 改成 "diamond",收尾会用 "diamond" 过滤只含旧槽位子集的缓存 → 结果集是旧搜索内容按新词过滤的不完整集合。baseOrder 相等检查不覆盖 search 变化。GUI 无此问题(搜索词变化直接重建 order)。建议:syncedBase 快照同时记录触发时的 search,收尾时若 search 已变,跳过过滤直接等下一轮 refresh(或重新触发 syncFullContents)。实际上 refresh 每 20 tick 会自然纠正,最终一致,但用户看到的是长达 20 tick 的错误结果
  3. 客户端按 getHoverName() 过滤与 GUI 不一致的潜在语义差
    applyClientFilteritemStack.getHoverName().getString()——含自定义命名(玩家给物品改的名)。GUI 的 StorageScreen.applySearchFilterstack.toStack().getHoverName()——同一来源。二者一致 ✅。但服务端 createOrderSortMode.NAME 下用 stack.getHoverName().getString() 排序,客户端 getComparator 也用 hover name——一致 ✅。无问题,仅确认。

⚠️ 警告

  • syncVisible 空页分支的缓存保留条件有漏洞:当 order(展示列表)为空时,若当前是普通文本搜索(过滤结果为空),缓存被保留(合理);但若此时用户清除搜索词(变空搜索),search 变为空 → isPlainTextSearch 返回 false → 清空缓存。可是 refresh 尚未把 order 更新为服务端全量——存在一瞬:搜索框已空、order 仍为空、缓存被清空。不过 refresh 很快会用服务端全量重建并 syncVisible 重新同步,属暂时性闪烁,非持久错误。低危。

💡 建议(非阻塞)

  • fullSyncing 只防并发不防悬挂:RPC 无超时(StorageClientStub.sync 无 timeout/exceptionally 链)。若某次 sync 响应丢失且服务端无后续刷新触发,fullSyncing 卡 true → 后续 refresh 的 syncFullContents 全部 early-return,普通文本搜索永久不更新。虽有 REFRESH_INTERVAL=20 周期刷新,但周期刷新只有在 baseOrder 变化时才重新同步——若 baseOrder 不变(存储静止 + 排序相同),syncFullContents 会因 fullSyncing=true 直接返回,永不收尾 → 搜索卡死。建议:fullSyncing 加时间戳超时,或收尾/超时路径统一兜底。
  • trimContentCache 的快路径优化条件keep.size() == CONTENTS.size() 时遍历所有 key 验证——逻辑正确但可简化为直接 removeIf。微小优化,非必需。
  • 本地化名称过滤的 Locale 处理toLowerCase(Locale.ROOT) 对土耳其语等特殊 locale 的 I 点问题免疫 ✅(GUI 同用 ROOT)。

🟢 看起来不错

  • FULL_SYNC_PAGE=256 与服务端 MAX_SYNC_SLOTS=256 上限精确对齐,分页正确
  • 服务端 sync 对越界/重复槽位安全跳过,客户端过期缓存不会崩溃
  • reset() 正确清理 baseOrderfullSyncing 状态,悬停切换不残留
  • 失败路径回退到服务端排序直接展示(未过滤),保证至少可用
  • StorageServerStubview != null 删除安全(fromStorage 仅在 view != null 分支内累加,属冗余判空清理)
  • 客户端过滤与 GUI applySearchFilter 逻辑一致,修复方向正确

结论: COMMENT — 修复思路正确、实现细致,转草稿说明作者已知晓有未竟之处。关键问题是搜索词切换/存储变动期间的最终一致收敛fullSyncing 悬挂风险。建议先处理 #1/#2(收敛性)与 fullSyncing 超时兜底。

不过,在发布前我应该再核实一个点:converted_to_draft 状态下应只报严重问题。同时按约束 7 检查标题是否准确——「Fix cannot search item name in terminal overlay」描述准确,无需修改。

最终审查意见如下。

@Gugle2308

Copy link
Copy Markdown
Collaborator

💾 Self-improvement review: Patched SKILL.md in skill 'anvilcraft-storage-system-review' (1 replacement).

@Gugle2308

Copy link
Copy Markdown
Collaborator

代码审查摘要 — PR #4688

操作: synchronize
范围: 2 个文件 (2 Java, 0 新增, 0 删除) / 380 行 diff(+273/−19)

概览

修复终端浮窗内普通文本搜索无法过滤物品的问题。根因:TerminalRemoteOverlay 的搜索框文字此前直接传入服务端 terminalReordercreateOrder,而 createOrder 对普通文本(非 @/# 前缀)本来就不做名称过滤(服务端无客户端语言环境,普通文本一律 matchesFilters 返回 true,等待 StorageScreen 客户端侧 applySearchFilter 二次过滤)——但浮窗没有这层客户端过滤,于是输入名称时列表不变。修复方案:浮窗为普通文本搜索引入「全量内容分页同步 → 客户端按本地化显示名 + id path 过滤」的机制。

审查依据(交叉验证)

  • StorageServerStub.createOrder(dev/1.21/1.6 基线上)对普通文本 search 不做过滤:matchesFilters 最后一条 || search.charAt(0) != '@' && ... 直接放行,仅 @namespace/#tag 前缀真正过滤。
  • StorageScreen.applySearchFilter / rebuildFoldedGroups 的客户端过滤语义:匹配 getHoverName() + BuiltInRegistries.ITEM.getKey(...).getPath(),与新增 applyClientFilter 一致(id path 一律 toLowerCase(Locale.ROOT),比 StorageScreen 主面板更严格但对小写化 path 无实际差异)。
  • 服务端 sync()MAX_SYNC_SLOTS = 256 上限校验(超限抛异常),新增分页 FULL_SYNC_PAGE = 256 与服务端上限对齐 ✅。

🔴 关键问题

  • TerminalRemoteOverlay.java — refresh()syncFullContents 早退后未同步可见页(内容可永远停留在旧页) — 重构后 refresh() 的回调在普通文本分支直接 syncFullContents(...)return(未调 syncVisible)。虽然 sync 完成后 applyClientFilter/applyServerOrder 会调 syncVisible,但 syncFullContentsfullSyncing 为真时仅置 fullSyncReplan=true 然后直接返回;若此刻可见页槽位内容缺失/过期,要等当前批结束且 applyClientFilter 才刷新——批处理中若又无 replan(如仅 search 不变但 baseOrder 未变),可能延迟一个批;极端情况下若当前批永不结束/连续 replan 被置位却无收尾消费,展示可陈旧。建议:syncFullContentsfullSyncing 分支在置 replan 后同步调用一次 syncVisible() 拉取当前可见页(与旧 refresh 行为对齐,成本仅一页)。

⚠️ 警告

  • TerminalRemoteOverlay.java — trimContentCacheCONTENTS.size() 短路优化存在隐患 — 当 keep.size() == CONTENTS.size() 时仍要求每个 key 都在 keep 中才跳过裁剪,这个检查本身正确(有 allKept 兜底);但 CONTENTS 只缓存同步过的槽位,基数可远小于 baseOrder.size(),此时 keep.size() != CONTENTS.size() 一般成立,会走 removeIf——正确但每 20 tick 对全量 keySet 迭代。大存储 + 长时间悬停时这是 O(槽位) 的每周期成本,建议仅在 orderChanged 时裁剪(缓存命中率已由保留交集保证)。非阻塞。
  • TerminalRemoteOverlay.java — fullSyncing 链在重入窗口可能短暂双重发送fullSyncing=true 置位与首个 StorageClientStub.sync 实际发出之间存在 ensureVirtualPos 异步空隙;若此间隙内用户再次输入触发 refresh,会走 fullSyncing 分支置 replan——没问题;但 replan 消费仅在「本批拉完收尾」时发生。若期间 isPlainTextSearch 短暂变 false 再变 true(如先删空再重打),分支切换可能让 replan 置位丢失。代码注释已说明设计意图(最终一致),属边缘竞态,风险低。
  • TerminalRemoteOverlay.java — 失败退避期 fullSyncRetryRefresh 复位为 0 后,若退避期内 contents 仍不齐,重试语义正确但可短暂显示未过滤列表 — 设计注释已明确这是降级策略(可接受),但建议在退避期结束恢复全量同步时,若上一次失败页已成功缓存部分内容,missing 重算基于 baseOrder 中未缓存槽位,正确(注释也说明了),无需改。

💡 建议

  • TerminalRemoteOverlay.java — applyClientFiltername/idPath 每次迭代做 toLowerCase + getHoverName 字符串构造 — 大存储全量过滤时每 20 tick 全量重扫所有槽位,且 itemStack.getHoverName() 可能触发组件解析。若后续出现卡顿,可考虑缓存「槽位→(name, idPath)」或仅在 orderChanged/search 变化时重过滤。
  • TerminalRemoteOverlay.java — isPlainTextSearch 判定与 createOrderrequiresName 有细微不对称 — 服务端 requiresName 还受 sort == NAME 影响(NAME 排序时需要 hover name 即使空搜索);浮窗 @/# 搜索 + SortMode.NAME 时服务端仍按名称排序,客户端过滤为空搜索/前缀搜索时跳过,语义一致(前缀搜索本来就服务端过滤),无实际 bug,仅为提示一致性已满足。

🟢 看起来不错

  • 架构合理:服务端保持无客户端语言环境的纯排序职责,客户端全量同步 + 二次过滤,与 StorageScreen 现有设计对齐。
  • 页预算(FULL_SYNC_PAGE_BUDGET = 4)与退避(FULL_SYNC_RETRY_REFRESHES = 2)有效防止大存储下 RPC 突发与失败重试放大。
  • 缓存保留交集(trimContentCache)而非全清,避免周期性刷新闪烁(旧代码在 orderChanged 时全清)。
  • 悬停目标切换(isHoveringSame)、搜索模式切换(非普通文本终止链)均有守卫,reset() 补齐所有新状态字段。
  • StorageServerStub.transferMaterialExactview != null 判空移除:fromStorage 只在 view != null 分支内累加(代码 1650/1678–1684),不变式成立,删除冗余判空是安全且更清晰的。
  • reorder() 显式切词立即清零退避计数,交互响应更及时。

📋 声称验证表

声称 状态 对应文件
修复浮窗无法按物品名称搜索 TerminalRemoteOverlay (全量同步+客户端过滤), StorageServerStub (注释同步)
客户端二次过滤与 StorageScreen 一致 applyClientFilter vs StorageScreen.applySearchFilter
服务端 sync 页大小对齐 FULL_SYNC_PAGE=256 vs MAX_SYNC_SLOTS=256

结论: COMMENT(倾向 APPROVE) — 修复方向正确、机制完整、边界处理周到;关键问题仅为可延迟到后续优化的刷新时序(非数据损坏/崩溃类),建议合并前补一个可见页同步的兜底调用,若确认无实际触发路径可忽略。

🧪 测试建议

被测目标 推荐测试场景 优先级
TerminalRemoteOverlay.refresh() 普通文本分支 搜索词逐字输入时浮窗内容跟随过滤、无闪烁/重复请求;切回 @/#/空搜索立即恢复 🔴
全量分页 syncFullPage >256 槽位存储触发多页;中途切走/切换搜索词不残留 stale 内容;页预算暂停后下周期续拉完整 🔴
trimContentCache 排序变化时保留交集、移除已消失槽位;内容缓存与 baseOrder 对齐 🟡
失败退避 sync 失败后降级为未过滤展示,退避结束自动恢复过滤 🟡
applyClientFilter 中文/大小写/含空格搜索词;id path 与 hover name 混合命中 🟡

由 Hermes Agent 审查

@Gugle2308

Copy link
Copy Markdown
Collaborator

💾 Self-improvement review: Patched references/pr4688-overlay-search-sync.md in skill 'anvilcraft-storage-system-review' (1 replacement). · Patched SKILL.md in skill 'anvilcraft-storage-system-review' (1 replacement).

@Gugle2308

Copy link
Copy Markdown
Collaborator

代码审查摘要 — PR #4688

操作: synchronize(第 3 版提交,相对基础分支 2 文件 / +217 −29)
范围: 2 个文件(2 Java, 0 新增, 0 删除)/ 384 行 diff
PR 标题: "Fix cannot search item name in terminal overlay 修复终端浮窗内无法搜索物品名称的问题"

问题背景

终端浮窗(TerminalRemoteOverlay)只缓存可见页槽位内容,此前普通文本搜索走服务端 reorder 过滤(服务端无客户端语言环境,只能按 id path 匹配),导致按中文/本地化物品名搜索永远为空。本 PR 让服务端对普通文本搜索返回全量排序,客户端分页拉全量内容后按显示名+id path 二次过滤——与 StorageScreen.applySearchFilter 语义对齐(目标分支基线上已确认该模式)。

设计亮点

  • 成本受控的全量同步:仅同步 baseOrder 中缺失槽位(syncFullContents 计算 missing),页大小 256 对齐服务端 MAX_SYNC_SLOTS,刷新周期页预算 4(FULL_SYNC_PAGE_BUDGET),避免几万槽位存储一次刷新背靠背几十个 RPC
  • 失败退避:页失败后 2 个刷新周期内降级为服务端排序展示(未过滤),退避期内不重复触发全量同步,避免周期性失败时每 20 tick 重发全部缺失页的流量放大
  • 最终一致的重排:全量同步期间排序/搜索词变化只置 fullSyncReplan 标记,收尾时基于最新 baseOrder 补一轮;悬停切换(isHoveringSame)终止整条链,reset() 已清状态
  • 缓存保留与裁剪trimContentCache 保留交集复用已缓存槽;普通文本搜索过滤结果为空时不再清空全量缓存(换词直接复用);fullSyncing 期间 orderChanged 裁剪后立即 syncVisible() 补拉可见页,避免停留在指向已裁剪槽位的空白页
  • 附带修复transferMaterialExact 回滚路径删除冗余 view != null 判空(fromStorage 仅在 view != null 分支内累加,不变式成立,安全)

⚠️ 警告(建议修复)

  1. 空过滤结果的 stale baseOrder 漂移syncVisible():展示为空且非普通文本搜索(服务端结果即空)时,旧代码清空 CONTENTS;现仅非普通文本时清空。若存储被外部清空(orderChanged=true 且新 baseOrder 为空),syncFullContentsmissing 为空 → 直接 applyClientFilterfiltered 空、syncVisible 普通文本分支不清缓存CONTENTS/COUNTS 保留旧槽位数据。后续进入 @/# 搜索或空搜索且服务端确实为空时,applyServerOrdertrimContentCache 裁剪,但 syncVisible 的 slots.isEmpty() 分支(非普通文本)会走 clear() —— 若展示为空是客户端过滤结果为空而服务端非空(此时普通文本分支不清),缓存得以保留。当前守卫看似有意的设计权衡,但需确认:普通文本搜索过滤结果为空 → 缓存保留 → 换新词过滤正确;若服务端为空 + 缓存未清CONTENTS 中残留槽位在过滤时因不在 baseOrder 中不会被采用——安全但建议注释说明(缓存过期数据仅供槽位索引过滤,不直接展示,残留无泄漏风险)。低风险,可接受。

  2. 降级展示期间普通文本搜索的槽位漂移 — 全量同步失败退避期 applyServerOrder(baseOrder 拷贝, false) 直接展示未过滤的服务端全量排序。若存储很大(>一页),降级期间翻页会看到"搜索词不匹配"的物品(服务端已按普通文本返回全量)。属预期降级行为(注释已说明),但用户观感上"搜索未生效"。建议:退避展示期间在浮窗加一个短暂"搜索过滤暂不可用"的角标/提示(可选非阻塞)。

  3. 服务端 terminalReorder 注释未同步StorageServerStub.java:1914 仅新增注释("普通文本搜索由客户端二次过滤"),但 terminalReorder普通文本搜索仍调用 createOrder(view, options, search, listed),其内部 matchesFilters 对普通文本恒 true(正确——服务端不过滤)。但 createOrder 对普通文本的排序键 requiresName 会取 getHoverName 参与排序——排序本身无碍,但服务端每次 reorder 都会全量 getHoverName(多语言环境开销),建议确认无性能热点(浮窗 reorder 周期 20 tick,若大存储每次全量计算 hover name 可能成为卡顿源)。⚠️ 提示,非阻塞。

💡 建议(非阻塞)

  • 代码重复applyClientFilterStorageScreen.applySearchFilter / rebuildFoldedGroups 的过滤逻辑(name/idPath contains)几乎一致。可提取共享工具(如 StackNameMatcher.matches(stack, plain))供 StorageScreen 与 Overlay 复用,避免两处语义漂移。
  • Locale.ROOT 一致性applyClientFilteridPath.toLowerCase(Locale.ROOT),而 StorageScreen.applySearchFilter 对 idPath toLowerCase(基线上 idPath.contains(search) 直接比较)。若用户在 StorageScreen 搜索大写 ID(如 @ 前缀不区分大小写由服务端处理),overlay 端 idPath 小写后与 search(也已小写)比较更宽容——行为略不一致但更宽松,无功能回归。
  • 提交信息粒度:3 个 commit 均为 "fix: fix bugs",建议 squash 或改描述性信息(对 reviewers 可追溯性)。
  • StorageServerStubif (fromStorage > 0) 移除 view != null:依赖不变式已注释,安全;建议加一行断言或保留判空以防御未来重构(有人将来在 view==null 分支累加 fromStorage 时静默 NPE)。

🟢 看起来不错

  • 分页、预算、退避、重排、裁剪的状态机设计缜密,注释详实(中文注释与逻辑完全对应)
  • 客户端过滤与服务端过滤路径边界清晰(普通文本 vs @/#/空)
  • applyClientFilter 保持服务端排序相对顺序、cursor clamp 一致
  • 服务端对 >256 槽位同步有 IllegalArgumentException 硬上限,客户端页大小 256 对齐正确
  • reset() 完整清空全部新增状态字段

📋 声称验证表

声称 状态 对应文件
修复终端浮窗无法按物品名搜索 TerminalRemoteOverlay (客户端过滤+全量同步), StorageServerStub (注释)

结论: APPROVE — 设计合理、边界覆盖完整(失败退避、重排 replan、悬停切换、页预算暂停),无阻塞性问题。建议修复项均低风险,可在后续迭代处理。注意:3 个 commit 都是 "fix: fix bugs",建议 squash 合并。


由 Hermes Agent 审查

@QiuShui1012
QiuShui1012 marked this pull request as ready for review September 2, 2026 11:34
@Gugle2308

Copy link
Copy Markdown
Collaborator

💾 Self-improvement review: Patched references/pr4688-overlay-search-sync.md in skill 'anvilcraft-storage-system-review' (1 replacement). · Patched SKILL.md in skill 'anvilcraft-storage-system-review' (1 replacement).

@PigeonNian
PigeonNian merged commit 3639413 into Anvil-Dev:dev/1.21/1.6 Sep 2, 2026
2 checks passed
@Gugle2308

Copy link
Copy Markdown
Collaborator

PR #4688 状态为 closed。根据审查约束第 1 条(closed/labeled 操作 → 停止,不发布评论),本次不执行 diff 下载与审查,也不输出任何评论内容。

无需进一步操作。

@Gugle2308

Copy link
Copy Markdown
Collaborator

代码审查摘要 — PR #4688

操作: ready_for_review
范围: 2 个文件(2 Java, 0 新增, 0 删除)/ 384 行 diff
标题: Fix cannot search item name in terminal overlay(修复终端浮窗内无法搜索物品名称)

📋 概述

PR 修复终端浮窗(TerminalRemoteOverlay)悬停时搜索物品名称无效的问题。根因与修复路径清晰:StorageScreen 的普通文本搜索走"服务端返回全量排序 + 客户端 applySearchFilter 按本地化名称二次过滤"(服务端无客户端语言环境,只处理 @namespace/#tag 前缀),而浮窗此前把普通文本搜索词原样发给服务端、直接信任返回结果,导致按中文显示名搜不到。修复为客户端补上同等的"分页全量同步 + 本地化名称/id path 二次过滤",并附带了一处服务端死代码清理。

3 个 commit 均为本 PR 分支内迭代(d4e0c00 主体 + 两轮 "fix bugs" 补丁),第 2 次提交把最后一个 commit 标为修复 bug 却最终改成了删掉 view 判空。经核对:这是第 1 次提交中引入的同一处 if (fromStorage > 0 && view != null)if (fromStorage > 0)net 0 变更,PR 最终与最初设计一致。

🔍 正确性核查

服务端 transferMaterialExactview 判空移除(StorageServerStub.java:1695) — 唯一"行为变更",经代码路径审计是安全的:

  • viewtarget.view()(本类 getView()),代码中从未为 null(每个 return 分支均产出非空 StorageView,见 getView 的 switch 全分支覆盖 + emptyView(player) 兜底)
  • fromStorage 仅在上方 if (moved < needed && view != null) 取存储分支内累加,故 fromStorage > 0 ⟹ view != null 蕴含成立 ✅
  • 移除死判空不改语义,无 NPE 风险

客户端分页全量同步 + 二次过滤(TerminalRemoteOverlay.java) — 多页批处理状态机,逐路径推演:

  • sync 服务端 MAX_SYNC_SLOTS=256 上限与 FULL_SYNC_PAGE=256 页大小匹配 ✅;空列表页 → 服务端返回空 updates 无副作用
  • 缺槽续拉正确:missing 每页从 missing.subList(from,to) 切页,页预算(4 页)达上限后暂停、fullSyncing=false,下一个 refresh 周期用新 baseOrder 重算缺失再续拉 → 大存储多轮收敛 ✅
  • 过滤与 StorageScreen.applySearchFilter 语义对齐:strip().toLowerCase(ROOT),匹配本地化显示名 + BuiltInRegistries.ITEM id path,保持服务端相对顺序 ✅(仅差 id path 未小写,等价)
  • fullSyncReplan 竞态消费路径、orderChangedtrimContentCache 缓存对齐、页面预算暂停期间 syncVisible 补拉可见页、退避降级后自动恢复等错误路径均有处理 ✅
  • cursor 越界后总被 Mth.clamp 收回到过滤后范围 ✅

⚠️ 警告

  • commit 信息与变更不一致(2 次)1eca5285383ddae 均名为 "fix: fix bugs",其中一次实际是删除了上一 commit 引入的 view != null 判空,另一次是补 syncVisible 竞态保护——都不算 bug fix,且与该 PR 标题/主题无关。建议合并前 rebase 压缩为单一语义化 commit(如 fix(storage): fix cannot search item name in overlay),便于追溯。非阻塞。

💡 建议

  • TerminalRemoteOverlay.java:658 trimContentCache() — 直接 CONTENTS.keySet().removeIf(...) 即可,无需先做等大小全包含判断的短路径优化(该判断在已裁剪场景下是纯浪费的二次遍历);语义正确,仅简化。
  • 缓存未用 COUNTS 复核 — 若最终想更精确可同时校验 count(当前非必要,跳过 O(n) 遍历是合理取舍)。
  • 大存储(数万槽位)首搜时需多轮 20-tick 刷新才能拉齐全部内容完成过滤——可考虑搜索词变化时临时提升拉取预算或缩短刷新间隔,改善首次搜索体验(非阻塞)。

🟢 看起来不错

  • 搜索词/排序变化时的最终一致性设计扎实(replan 标记 + 批预算 + 失败退避 + 自动恢复),无明显遗漏路径
  • 悬停切换(isHoveringSame)+ reset() 全状态清理对异步链的作废处理完整,fullSyncing/fullSyncReplan/fullSyncPages 等状态在 reset 中均有复位
  • 服务端"普通文本搜索不过滤"的注释与实现已与 StorageScreen 路径一致(该行为在 base 分支已存在,本 PR 补齐客户端一半)
  • 移除判空处添加的不变式注释清晰

📋 声称验证

声称 状态 对应实现
修复终端浮窗内无法按名称搜索物品 TerminalRemoteOverlay:syncFullContents/syncFullPage/applyClientFilter + StorageServerStub 注释说明客户端过滤职责

🧪 测试建议

被测目标 推荐测试场景 优先级
syncFullContents/syncFullPage 槽位数 > 256(跨多页)、页预算暂停后下一周期续拉、排序/搜索词在批处理中变化触发 replan 🔴
applyClientFilter 中文/英文显示名匹配、@/# 前缀搜索不进入客户端过滤、id path 匹配 🔴
applyServerOrder 退避降级 sync 失败后按服务端排序展示、退避结束自动恢复过滤 🟡
sync 空槽/越界 missing 列表为空、槽位越界(服务端跳过) 🟢
trimContentCache 排序变化后缓存交集保留、移除已不在 baseOrder 的槽 🟢
transferMaterialExact 材料不足回滚时存储部分放回(验证判空移除无回归) 🟢

结论: APPROVE — 核心修复(客户端本地化名称二次过滤)逻辑正确且与既有 StorageScreen 路径对齐;唯一行为变更(view 判空移除)经代码路径审计确认安全,注释说明了不变式。提交历史中的 "fix: fix bugs" 命名建议清理(非阻塞)。


由 Hermes Agent 审查

@Gugle2308

Copy link
Copy Markdown
Collaborator

💾 Self-improvement review: Patched SKILL.md in skill 'github-webhook-pr-review' (1 replacement).

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