diff --git a/app/MaaFW/ArknightWin32.py b/app/MaaFW/ArknightWin32.py index b19e872c4..cde97db4a 100644 --- a/app/MaaFW/ArknightWin32.py +++ b/app/MaaFW/ArknightWin32.py @@ -21,6 +21,7 @@ import os +import math import time import ctypes import psutil @@ -46,6 +47,34 @@ logger = get_logger("明日方舟PC工具") +# 定时任务每秒触发一次,连接失败后若不退避就会以秒级频率反复重试并刷屏; +# 上限取 60 秒,保证用户随时进入游戏后仍能在一分钟内自动接上。 +CONNECT_RETRY_BASE_SECONDS = 2.0 +CONNECT_RETRY_MAX_SECONDS = 60.0 +# 指数要先封顶:失败次数一直累加时 2 ** (failures - 1) 会在第 1025 次转 float 溢出, +# 异常从 except 块里抛出会直接终止每秒定时任务;封顶到刚越过上限的那一档即可。 +_CONNECT_RETRY_MAX_EXPONENT = math.ceil( + math.log2(CONNECT_RETRY_MAX_SECONDS / CONNECT_RETRY_BASE_SECONDS) +) + + +def connect_retry_delay(failures: int) -> float: + """ + 按连续失败次数计算下次重试前的等待秒数 + + Args: + failures: 已连续失败的次数, 首次失败传 1 + + Returns: + float: 等待秒数, 指数增长并封顶到 ``CONNECT_RETRY_MAX_SECONDS`` + """ + + exponent = min(max(failures - 1, 0), _CONNECT_RETRY_MAX_EXPONENT) + return min( + CONNECT_RETRY_BASE_SECONDS * 2**exponent, + CONNECT_RETRY_MAX_SECONDS, + ) + class _ArknightWin32Toolkit: def __init__(self): @@ -53,6 +82,9 @@ def __init__(self): self.arknights_hwnd = -1 self.arknights_window = None + self.connect_failures = 0 + self.next_connect_attempt = 0.0 + self.tasker = Tasker() self.listener = keyboard.Listener() @@ -101,6 +133,8 @@ async def scheduled_task(self) -> None: if self.arknights_hwnd != new_hwnd: self.arknights_hwnd = new_hwnd + # 窗口发生变化意味着新的连接机会,清掉上一轮的退避 + self.reset_connect_backoff() if new_hwnd == 0: logger.warning("未检测到明日方舟窗口,暂停任务器") @@ -110,9 +144,19 @@ async def scheduled_task(self) -> None: else: await self.connect_arknights() - if not self.get_connect_status() and self.arknights_hwnd > 0: + if ( + not self.get_connect_status() + and self.arknights_hwnd > 0 + and time.monotonic() >= self.next_connect_attempt + ): await self.connect_arknights() + def reset_connect_backoff(self) -> None: + """清空连接失败计数与退避窗口""" + + self.connect_failures = 0 + self.next_connect_attempt = 0.0 + def get_connect_status(self) -> bool: """获取连接状态""" @@ -144,15 +188,24 @@ async def connect_arknights(self) -> None: keyboard_method=MaaWin32InputMethodEnum.Seize, ) logger.success("已连接到明日方舟") + self.reset_connect_backoff() except Exception as e: - logger.error(f"连接明日方舟失败: {e}") - await Publisher.send( - id=protocol.ID_ARKNIGHTS_PC_TOOLKIT, - type=protocol.TOOLKIT_NOTICE, - data=WSTaskNoticeData( - level="error", message=f"无法连接明日方舟: {str(e)}" - ), + self.connect_failures += 1 + delay = connect_retry_delay(self.connect_failures) + self.next_connect_attempt = time.monotonic() + delay + logger.error( + f"连接明日方舟失败(第 {self.connect_failures} 次," + f"{delay:.0f} 秒后重试): {e}" ) + # 仅首次失败提示用户,避免重试期间反复弹出同一条通知 + if self.connect_failures == 1: + await Publisher.send( + id=protocol.ID_ARKNIGHTS_PC_TOOLKIT, + type=protocol.TOOLKIT_NOTICE, + data=WSTaskNoticeData( + level="error", message=f"无法连接明日方舟: {str(e)}" + ), + ) def on_key_release(self, key: keyboard.Key | keyboard.KeyCode | None) -> None: """pynput 回调""" diff --git a/res/version.json b/res/version.json index d8c6f56e0..fa11417af 100644 --- a/res/version.json +++ b/res/version.json @@ -31,7 +31,8 @@ "前端类型治理 清理脚本与 Electron 基础边界中的 any,并修复 BetterGI 设置会话无法接收完成和错误事件的问题", "HSR专项 修复 MAS 管控任务的文本与 JSON 配置项在输入过程中被重渲染清空、导致只有数字项能填写的问题(如货币战争的首领/投资重开条件、策略文件、开拓者名称) by [@qiyinxi](https://github.com/qiyinxi)", "OK-WW专项 修复任务报告推送因漏传通知用户配置参数而失败的问题 by [@AthenaHibou](https://github.com/AthenaHibou)", - "游戏签到 修复通知返回值改为 DispatchResult 后未同步两处消费方、开启签到通知时执行签到必报 TypeError 的问题(手动签到接口返回 500,慢渠道后台通知路径丢失日志) by [@1w1w11w1](https://github.com/1w1w11w1)" + "游戏签到 修复通知返回值改为 DispatchResult 后未同步两处消费方、开启签到通知时执行签到必报 TypeError 的问题(手动签到接口返回 500,慢渠道后台通知路径丢失日志) by [@1w1w11w1](https://github.com/1w1w11w1)", + "修复明日方舟PC工具连接失败后每秒重试并反复弹出错误提示的问题" ] }, "v5.5.0-beta.2": {