fix: 修复 Hytale 等平台适配正确性#716
Conversation
Code Review — #716 fix: 修复 Hytale 等平台适配正确性这个 PR 修的几处都是真 bug,其中 但有一个发现需要优先说:本 PR 用 WeakHashMap 修了 Hytale 的 另有一处 审阅方式:读 patch + 对照源码逐条验证 + 实测复现(回调丢失、CME 类问题)。未实跑 gradle 测试。 🔴 问题 1 —
|
| 项 | 结论 |
|---|---|
SimpleCommand 父节点执行器丢失 |
旧代码 if (body.children.isEmpty()) body.func(this) else children.forEach { ... } 是 if/else——有子节点时父节点自己的 func 不执行。所以同时写了执行器和子命令的 @CommandBody 会静默丢掉父级执行器。新 registerTo 改成 func(this) 后再递归 children,两者都执行。真 bug 修复 |
registerTo 的递归正确性 |
this@registerTo.children.forEach { it.registerTo(this) },this 是新建的子 CommandComponent,限定符用得对,不会递归到自身 |
Bungee sendTitle 的 subtitle |
旧代码 it.subTitle(TextComponent(title ?: "")) 用的是 title 而非 subtitle。真 bug,bungeeTitleComponents 修对了 |
| Bungee aliases 从未注册 | 旧代码 object : Command(command.name, permission) 只传了两参,command.aliases 完全没用上。新 RegisteredBungeeCommand 走 Command(name, permission, *aliases) 三参重载。真 bug 修复 |
Bungee unregisterCommand 的 NPE |
旧 getProperty<...>("commandMap")!![command] 在反射拿不到 commandMap 时 !! 抛 NPE;新 ?.get(command) ?: return 安全返回。修得对 |
Bukkit bedSpawnLocation setter |
属性声明是 Location? 但 setter 里 value!!.toBukkitLocation(),传 null 即 NPE。改成 value?.toBukkitLocation() 与声明一致。真 bug 修复 |
| Velocity 双重包装 | adaptPlayer / adaptCommandSender 新增 is ProxyPlayer / is ProxyCommandSender 前置判断。旧代码 any as Player 在传入 VelocityPlayer 时会 ClassCastException。修得对,且顺序正确(先判代理再判 Player) |
Hytale future.get() 阻塞的危害 |
三处 CommandManager.get().handleCommand(...).get() 确认存在(HytaleCommandSender.kt:55,95、HytalePlayer.kt:327)。在命令处理线程上等命令 Future 是自死锁形态,移除方向正确 |
| Hytale 命令权限 | 新增 commandPermission(structure.permission) → permission.ifEmpty { null } + requirePermission(it)。旧代码完全没有处理 structure.permission,等于所有命令无权限校验。真修复 |
| Hytale 首参数原生补全 | 新增 withRequiredArg("argument", "", ArgTypes.STRING).suggest { ... }。旧代码只有 setAllowsExtraArguments(true),没有任何 suggest 注册,所以 Tab 补全在 Hytale 上完全不工作 |
| Hytale 无参变体 | addUsageVariant(object : CommandBase(description) { ... executeSync → executeCommand(context, emptyArray()) })。补上了"只输命令名不带参数"的执行路径 |
| Hytale sender 包装 | adaptNativeCommandSender 按 sender is Player 分派到 HytalePlayer / HytaleCommandSender。旧代码走 adaptCommandSender(context.sender()) 经过 PlatformAdapter,可能拿到错误类型 |
commandArguments 与旧实现等价性 |
旧代码 inputString.split(" ").filter { it.isNotBlank() } 后 drop(1);新代码 trim().split(Regex("\\s+")) 后同样 drop(1)。新版对 tab/多空格更健壮,语义一致 |
| Hytale executor 状态机 | NEW → RUNNING → STOPPED 单向;start() 在非 NEW 时直接 return;stop() 幂等;pendingTasks.filterNotTo { it.isCancelled } 过滤掉启动前已取消的任务。逻辑正确 |
| Hytale 异步事件 Future | 新增的 handler 包装:null 返回值转 completeExceptionally(NullPointerException),抛异常转 completeExceptionally(ex)。避免了 handler 返回 null 导致 NPE 或异常被吞 |
taskScheduler 测试接缝 |
private constructor 注入 HytaleTaskScheduler?,生产路径走 HytaleServerTaskScheduler。让测试能用 fake scheduler 而不碰 HytaleServer.SCHEDULED_EXECUTOR。设计合理 |
| reflex 与私有主构造器 | 与 #715 相同形态,我在 #715 已实测验证 reflex 1.2.4 的 newInstance() 能正确选中公开无参次构造器,HytaleExecutor 同样安全 |
activateQuitSession / fireQuitCallbacks 的同步 |
都在 synchronized(quitLock) 内操作两张 WeakHashMap,回调执行放在锁外(registered.forEach 在同步块之后),避免持锁调用用户代码。这一点做得对 |
registerQuitCallback 的竞态处理 |
已 completed 时立即执行(锁外),否则加入表。不会出现"注册时玩家已退出导致回调永不执行" |
| 测试覆盖 | 新增 8 个测试文件,覆盖 SimpleCommand 空树/父节点执行、Bukkit 空床点、Bungee alias/title、Velocity 双重包装、Hytale 命令参数/补全/权限、Hytale executor 状态机、Hytale listener、Hytale sender |
总结
SimpleCommand 父节点执行器丢失、Bungee subtitle 写错、Bungee aliases 从未注册、Hytale 权限与补全完全未实现、Bukkit bedSpawnLocation 强解包,这几处都是明确的功能缺陷,修得对。移除 Hytale 的 future.get() 阻塞也是正确判断。
建议处理:
- 问题 1(
onQuit回调丢失)——Hytale 修了,Bukkit / Bungee / Velocity 三处同样的 bug 未修。我实测复现了回调完全不触发。onQuit是插件清理玩家数据的常用入口,建议一并修掉,方案可直接复用本 PR 在 Hytale 上的做法。 - 问题 2(
performCommand恒返回 true)——去掉阻塞正确,但建议至少在dispatchCommand里挂whenComplete记录失败,否则命令执行失败完全静默;并把返回值语义变化写进兼容性说明。 - 问题 3(第四份状态机)——与 fix(platform): 修复生命周期终态与任务清理 #715 合并后会有四份近乎相同的 executor 实现,建议在 fix: 整合 Issue #703 稳定性修复并发布 6.3.1 #720 整合时抽公共基类。
- 🔵 a(
HytalePlayerinit 块会清掉已完成的 quit session 标记)建议一并看一下,adaptPlayer每次都 new,这个路径不罕见。
说明:本次审阅未实跑 gradle 测试(含 PR 描述列出的两条命令)。
onQuit回调丢失为本机实测复现。reflex 双构造器行为在 #715 审阅时已实测。其余结论基于 patch 与仓库源码推导,已逐条注明依据位置。
原有问题
Hytale 及其他平台适配层中存在阻塞等待、调度语义错误和平台对象包装不一致:
SimpleCommand空树或只有父节点时执行行为不一致。典型触发场景与后果
本 PR 修改
onQuit回调。SimpleCommand空树和父节点执行逻辑。修改目的
让平台抽象忠实反映各平台真实线程、命令、事件和对象语义,避免阻塞平台线程、类型包装错误及边界输入导致的崩溃。
兼容性与行为变化
验证
./gradlew :common-platform-api:test :platform:platform-bukkit-impl:test :platform:platform-bungee-impl:test :platform:platform-velocity-impl:test :platform:platform-hytale:test --rerun-tasks --no-parallel./gradlew :common-platform-api:build :platform:platform-bukkit-impl:build :platform:platform-bungee-impl:build :platform:platform-velocity-impl:build :platform:platform-hytale:build --rerun-tasks --no-parallelgit diff --checkFuture.get、.join()、Thread.sleep或CountDownLatchRefs #703