BurstDrive 挂 OnUpdateEvent 每 tick 对背包每把刀做 NBT 读取,缺'是否注册本特效'前置过滤 #2

Closed
opened 2026-09-08 18:47:26 +00:00 by layue13 · 1 comment
Owner

问题描述

BurstDrive.onUpdateItemSlashBlade(:60-93)挂在主仓 OnUpdateEvent(服务端每玩家每 tick 派发,背包内每把刀一次):方法先做 getItemTagCompound + getComboSequence + isEffective 等多次 NBT 读取,之后才判定特效是否生效。特效注册状态持久化在刀 NBT(SB.SEffect)中,可廉价前置过滤——当前顺序使未注册该特效的刀也每 tick 付出全部 NBT 读取。

技术分析

代码位置:

  • 文件:src/main/java/mods/flammpfeil/slashblade/kirisaya/specialeffect/BurstDrive.java:60-93

调用链:
主仓 SlashBladeHooks.EventBus OnUpdateEvent(每 tick × 每把刀)
→ onUpdateItemSlashBlade
→ getItemTagCompound + getComboSequence + isEffective(NBT 复合查找×3-4)
→ 特效判定(大部分刀在此短路)

影响规模

每玩家背包 B 把刀 × 20 tps 次 3-4 层 NBT 查找。20 人 × 5 把 = 300-400 次 NBT 复合查找/秒。单次小,列低危。

根因分析

Tick 设计:检查顺序未把最廉价的判定(SB.SEffect key 存在性)前置。

优化方向

先 tag.hasKey("SB.SEffect") && tag.getCompoundTag("SB.SEffect").hasKey(本特效key) 短路,再走 combo/isEffective。保持行为不变。完全兼容。

验证方案

Spark 对比 OnUpdateEvent 处理占比。
另附带:witchMagic 粒子在服务端调用为 no-op(永远不可见),可顺手清理。

## 问题描述 BurstDrive.onUpdateItemSlashBlade(:60-93)挂在主仓 OnUpdateEvent(服务端每玩家每 tick 派发,背包内每把刀一次):方法先做 getItemTagCompound + getComboSequence + isEffective 等多次 NBT 读取,之后才判定特效是否生效。特效注册状态持久化在刀 NBT(SB.SEffect)中,可廉价前置过滤——当前顺序使未注册该特效的刀也每 tick 付出全部 NBT 读取。 ## 技术分析 代码位置: - 文件:src/main/java/mods/flammpfeil/slashblade/kirisaya/specialeffect/BurstDrive.java:60-93 调用链: 主仓 SlashBladeHooks.EventBus OnUpdateEvent(每 tick × 每把刀) → onUpdateItemSlashBlade → getItemTagCompound + getComboSequence + isEffective(NBT 复合查找×3-4) → 特效判定(大部分刀在此短路) ## 影响规模 每玩家背包 B 把刀 × 20 tps 次 3-4 层 NBT 查找。20 人 × 5 把 = 300-400 次 NBT 复合查找/秒。单次小,列低危。 ## 根因分析 Tick 设计:检查顺序未把最廉价的判定(SB.SEffect key 存在性)前置。 ## 优化方向 先 `tag.hasKey("SB.SEffect") && tag.getCompoundTag("SB.SEffect").hasKey(本特效key)` 短路,再走 combo/isEffective。保持行为不变。完全兼容。 ## 验证方案 Spark 对比 OnUpdateEvent 处理占比。 另附带:witchMagic 粒子在服务端调用为 no-op(永远不可见),可顺手清理。
Author
Owner

修复记录

基线与实际问题:基线为 main@27199534f47e2bcd8ecb16d95ff9001119bd9f8c。BurstDrive 在服务端每把 SlashBlade 的更新事件中,会先取得根 NBT 并调用通用 isEffective;未登记 BurstDrive 的刀也会进入该通用查询。实际代码中 getComboSequence 位于 isEffective 之后,而非原 Issue 描述的之前;通用特效查询会为缺失项懒创建语义为空的 SB.SEffect 复合标签。

方案与取舍:在保留根 NBT 获取后,先检查 SB.SEffect 是否为复合标签,再检查其中是否存在 BurstDrive 键。无标签或无键的刀直接返回;有键的刀继续使用原有 isEffective 及后续逻辑。没有清理服务端 witchMagic 调用:该项不属于本 Issue 的主要成本,且尚未在真实集成中验证其对世界访问器的可观察影响。

行为契约与允许差异:已登记 BurstDrive 的刀仍通过原有公共特效 API 判定,损坏状态、玩家条件、粒子、连段和攻击时序均未改动;零值和异常数值类型的已存在键也继续走原 API。错误类型的 SB.SEffect 原本解析为空并得到 None,现在同样返回。允许的内部持久化差异仅是未登记的刀不再因该监听器写入语义为空的 SB.SEffect 子标签;它原本不会激活任何特效或改变玩法数据。未改变 API/ABI/协议,无需下游适配。

性能证据:静态计数。未登记刀的每个服务器更新由 1 次通用 isEffective/getSpecialEffect 查询及潜在空子标签写入,变为 2 次 NBT 键存在性检查后返回;已登记路径不变。收益随玩家背包中未登记刀的数量和更新频率线性增长。未进行真实服务器 TPS 或 Spark 实测。

验证:python3 tests/regression/burst_drive_effect_registration_regression.py 通过,覆盖过滤顺序、空数据、已登记键与异常数值键的分流;git diff --check 通过;./gradlew --no-daemon compileJava reobfJar --console=plain 构建成功。未在 Forge 多人运行环境实测粒子及攻击体验。

关联提交与目标分支:7b31dd5ab84e2665842cd1e9b70a6a861a7c7256,已推送至默认分支 main。

完成范围:已消除本 Issue 所述的未登记 BurstDrive 刀进入通用特效查询的主要重复成本;已登记特效路径保持原状。

回滚:在 main 上反转提交 7b31dd5ab84e2665842cd1e9b70a6a861a7c7256;这会恢复未登记刀的通用查询及空子标签写入。

关闭依据:主要成本已由入口注册过滤完整覆盖,构建和定向回归均通过;无待协调的下游改动。

## 修复记录 基线与实际问题:基线为 `main@27199534f47e2bcd8ecb16d95ff9001119bd9f8c`。`BurstDrive` 在服务端每把 SlashBlade 的更新事件中,会先取得根 NBT 并调用通用 `isEffective`;未登记 BurstDrive 的刀也会进入该通用查询。实际代码中 `getComboSequence` 位于 `isEffective` 之后,而非原 Issue 描述的之前;通用特效查询会为缺失项懒创建语义为空的 `SB.SEffect` 复合标签。 方案与取舍:在保留根 NBT 获取后,先检查 `SB.SEffect` 是否为复合标签,再检查其中是否存在 `BurstDrive` 键。无标签或无键的刀直接返回;有键的刀继续使用原有 `isEffective` 及后续逻辑。没有清理服务端 `witchMagic` 调用:该项不属于本 Issue 的主要成本,且尚未在真实集成中验证其对世界访问器的可观察影响。 行为契约与允许差异:已登记 BurstDrive 的刀仍通过原有公共特效 API 判定,损坏状态、玩家条件、粒子、连段和攻击时序均未改动;零值和异常数值类型的已存在键也继续走原 API。错误类型的 `SB.SEffect` 原本解析为空并得到 `None`,现在同样返回。允许的内部持久化差异仅是未登记的刀不再因该监听器写入语义为空的 `SB.SEffect` 子标签;它原本不会激活任何特效或改变玩法数据。未改变 API/ABI/协议,无需下游适配。 性能证据:静态计数。未登记刀的每个服务器更新由 1 次通用 `isEffective/getSpecialEffect` 查询及潜在空子标签写入,变为 2 次 NBT 键存在性检查后返回;已登记路径不变。收益随玩家背包中未登记刀的数量和更新频率线性增长。未进行真实服务器 TPS 或 Spark 实测。 验证:`python3 tests/regression/burst_drive_effect_registration_regression.py` 通过,覆盖过滤顺序、空数据、已登记键与异常数值键的分流;`git diff --check` 通过;`./gradlew --no-daemon compileJava reobfJar --console=plain` 构建成功。未在 Forge 多人运行环境实测粒子及攻击体验。 关联提交与目标分支:[`7b31dd5ab84e2665842cd1e9b70a6a861a7c7256`](https://home.potatolab.site:3000/Archive/slashblade-kirisaya/commit/7b31dd5ab84e2665842cd1e9b70a6a861a7c7256),已推送至默认分支 `main`。 完成范围:已消除本 Issue 所述的未登记 BurstDrive 刀进入通用特效查询的主要重复成本;已登记特效路径保持原状。 回滚:在 `main` 上反转提交 `7b31dd5ab84e2665842cd1e9b70a6a861a7c7256`;这会恢复未登记刀的通用查询及空子标签写入。 关闭依据:主要成本已由入口注册过滤完整覆盖,构建和定向回归均通过;无待协调的下游改动。
layue13 added the
type
perf
priority
p3
labels 2026-10-06 21:07:43 +00:00
layue13 changed title from [性能][低] BurstDrive 挂 OnUpdateEvent 每 tick 对背包每把刀做 NBT 读取,缺'是否注册本特效'前置过滤 to BurstDrive 挂 OnUpdateEvent 每 tick 对背包每把刀做 NBT 读取,缺'是否注册本特效'前置过滤 2026-10-06 21:07:43 +00:00
This repo is archived. You cannot comment on issues.
No labels
priority
p3
type
perf
1 Participants
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Archive/slashblade-kirisaya#2