From deb5fca7f8f87c1fcea847eceb14eb475ed95923 Mon Sep 17 00:00:00 2001 From: Hpero4 Date: Fri, 16 Aug 2024 00:33:34 +0800 Subject: [PATCH 1/4] =?UTF-8?q?1.=20FIX=20=E8=BF=9E=E7=BB=AD=E5=9C=A8?= =?UTF-8?q?=E5=80=99=E9=80=89=E6=A0=8F=E9=80=89=E4=B8=AD=E4=B8=A4=E6=AC=A1?= =?UTF-8?q?=E7=9B=B8=E5=90=8C=E5=8F=AC=E5=94=A4=E5=B8=88=E4=BC=9A=E8=B5=B7?= =?UTF-8?q?=E4=B8=A4=E4=B8=AAtask=E5=90=8C=E6=97=B6=E5=8A=A0=E8=BD=BD?= =?UTF-8?q?=E5=AF=BC=E8=87=B4=E5=AE=A2=E6=88=B7=E7=AB=AF=E9=97=AA=E9=80=80?= =?UTF-8?q?=202.=20FIX=20=E4=BD=BF=E7=94=A8cancel()=E6=9C=89=E6=97=B6?= =?UTF-8?q?=E5=80=99=E6=97=A0=E6=B3=95=E7=BB=88=E6=AD=A2task=203.=20?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=88=98=E7=BB=A9=E6=90=9C=E7=B4=A2UI?= =?UTF-8?q?=E5=93=8D=E5=BA=94=E9=80=9F=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/lol/connector.py | 7 +++++++ app/view/search_interface.py | 35 +++++++++++++++++++++++++++++++---- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/app/lol/connector.py b/app/lol/connector.py index 1501353..5d24256 100644 --- a/app/lol/connector.py +++ b/app/lol/connector.py @@ -3,6 +3,7 @@ import json import threading import re +from asyncio import CancelledError from collections import deque import requests @@ -86,6 +87,12 @@ async def wrapper(*args, **kwargs): try: async with connector.semaphore: res = await func(*args, **kwargs) + except CancelledError: + # Fix: 使用task.cancel()偶尔会停不下task -- By Hpero4 + # 在调用cancel()时, 会从调用栈的最底抛出CancelledError, 最终传递到loop终止task; + # 由于CancelledError是BaseException的子类, + # 若task恰好跑到被retry装饰的函数中, 会被retry中的BaseException捕获并吞掉, 从而无事发生 + raise except BaseException as e: time.sleep(retry_sep) exce = e diff --git a/app/view/search_interface.py b/app/view/search_interface.py index 2139093..f6da9a3 100644 --- a/app/view/search_interface.py +++ b/app/view/search_interface.py @@ -11,6 +11,8 @@ QSpacerItem, QSizePolicy, QLabel, QStackedWidget, QWidget) from PyQt5.QtCore import Qt, pyqtSignal from PyQt5.QtGui import QPixmap, QColor + +from ..common.logger import logger from ..common.qfluentwidgets import (SmoothScrollArea, PushButton, ToolButton, InfoBar, InfoBarPosition, ToolTipFilter, ToolTipPosition, isDarkTheme, FlyoutViewBase, Flyout, Theme, @@ -31,6 +33,23 @@ from ..components.seraphine_interface import SeraphineInterface +TAG = "SearchInterface" + + +def asyncLockDecorator(lockName): + """ + 给任何一个方法加锁, return时释放 + args[0]是self, 通过getattr找到lockName + """ + def decorator(func): + async def wrapper(*args, **kwargs): + lock = getattr(args[0], lockName) + async with lock: + return await func(*args, **kwargs) + return wrapper + return decorator + + class GamesTab(QFrame): tabClicked = pyqtSignal(str) gameDetailReady = pyqtSignal(dict) @@ -1008,6 +1027,8 @@ def __init__(self, parent=None): self.detailViewLoadTask = None self.loadingGameId = 0 + self.loadFirstPageLock = asyncio.Lock() + self.__initWidget() self.__initLayout() self.__connectSignalToSlot() @@ -1077,6 +1098,7 @@ def __showSummonerNotFoundMsg(self): parent=self ) + @asyncLockDecorator('loadFirstPageLock') async def searchAndShowFirstPage(self, puuid=None): name = self.searchLineEdit.text() if name == "": @@ -1086,6 +1108,8 @@ async def searchAndShowFirstPage(self, puuid=None): summoner = await connector.getSummonerByPuuid(name) else: summoner = await connector.getSummonerByName(name) + # Fix: 超快速的在候选栏选中两次同样puuid会起两个task加载战绩, 100%干掉客户端 -- By Hpero4 + puuid = summoner['puuid'] if 'errorCode' in summoner: self.__showSummonerNotFoundMsg() @@ -1105,9 +1129,7 @@ async def searchAndShowFirstPage(self, puuid=None): while not self.gameLoadingTask.cancelled() \ and not self.gameLoadingTask.done(): - # FIX: task有可能会错过cancel(), 导致必须等到查询结束; -- By Hpero4 - self.gameLoadingTask.cancel() - await asyncio.sleep(.2) + await asyncio.sleep(.3) self.puuid = summoner['puuid'] self.gamesView.gamesTab.clear() @@ -1132,7 +1154,6 @@ async def searchAndShowFirstPage(self, puuid=None): # 启动任务,往 gamesTab 里丢数据 # NOTE 既然创建新任务, 并且刷新了self.puuid 就应该用self的, 否则就违背了loadGames判断的初衷 - # FIXME: 当从两名召唤师之间反复横跳时, 有可能会导致一puuid起了2个(或更多)task -- By Hpero4 self.gameLoadingTask = asyncio.create_task( self.__loadGames(self.puuid)) @@ -1188,12 +1209,15 @@ async def __loadGames(self, puuid): begIdx = 20 endIdx = 29 + logger.debug(f"welcome load games task: {puuid}", TAG) + # NOTE 换了查询目标, 若之前正在查, 先等 task 被 release 掉 -- By Hpero4 while self.gameLoadingTask \ and not self.gameLoadingTask.done() \ and puuid != self.puuid: await asyncio.sleep(.2) + logger.debug(f"start load {puuid}", TAG) # 连续查多个人时, 将前面正在查的task给release掉 while self.puuid == puuid: # 为加载战绩详情让行 @@ -1205,6 +1229,7 @@ async def __loadGames(self, puuid): ): await asyncio.sleep(.2) + t1 = time.time() try: games = await connector.getSummonerGamesByPuuidSlowly( puuid, begIdx, endIdx) @@ -1213,7 +1238,9 @@ async def __loadGames(self, puuid): # NOTE 触发 SummonerGamesNotFound 时, 异常信息会通过 connector 下发到 main_window 的 __onShowLcuConnectError # 理论上会有弹框提示 -- By Hpero4 return + t2 = time.time() + logger.debug(f"load games {self.puuid} [{begIdx}-{endIdx}] finish {t2-t1}s", TAG) # 1000 局搜完了,或者正好上一次就是最后 # 在切换了puuid时, 就不要再把数据刷到Games上了 -- By Hpero4 if games['gameCount'] == 0 or self.puuid != puuid: From a2fa093aa0d5525819dc0245c79cba6342d01b37 Mon Sep 17 00:00:00 2001 From: Hpero4 Date: Sat, 17 Aug 2024 00:51:46 +0800 Subject: [PATCH 2/4] =?UTF-8?q?1.=20=E7=8E=B0=E5=9C=A8,=20=E5=9C=A8?= =?UTF-8?q?=E6=90=9C=E7=B4=A2=E6=A1=86=E6=8C=81=E6=9C=89=E7=84=A6=E7=82=B9?= =?UTF-8?q?=E6=97=B6=E5=86=8D=E6=AC=A1=E7=82=B9=E5=87=BB,=20=E4=BC=9A?= =?UTF-8?q?=E5=BC=B9=E5=87=BA=E5=80=99=E9=80=89=E6=A0=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/components/search_line_edit.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/components/search_line_edit.py b/app/components/search_line_edit.py index 8b43cf3..f09da95 100644 --- a/app/components/search_line_edit.py +++ b/app/components/search_line_edit.py @@ -154,3 +154,8 @@ def focusInEvent(self, e): if e.reason() != 4: self._showCompleterMenu() super().focusInEvent(e) + + def mousePressEvent(self, e): + if self.hasFocus(): + self._showCompleterMenu() + super().mousePressEvent(e) From 5af939075fea48e12ccdb2edb6e664b20fe41e77 Mon Sep 17 00:00:00 2001 From: Hpero4 Date: Sat, 17 Aug 2024 20:52:43 +0800 Subject: [PATCH 3/4] =?UTF-8?q?1.=20Fix:=20BUG#451=20=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E7=94=9F=E6=B6=AF=E8=83=8C=E6=99=AF=E6=97=B6=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/lol/connector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lol/connector.py b/app/lol/connector.py index 5d24256..5eb7f39 100644 --- a/app/lol/connector.py +++ b/app/lol/connector.py @@ -331,7 +331,7 @@ def __initFolder(self): "profile icons", "rune icons", "summoner spell icons", - "augment icons" + "augment icons", "splashes", ]: p = f"app/resource/game/{folder}" From ab80fa7c05671a2b3b28c5617955917b6d1ee536 Mon Sep 17 00:00:00 2001 From: Hpero4 Date: Mon, 19 Aug 2024 12:48:42 +0800 Subject: [PATCH 4/4] =?UTF-8?q?1.=20Fix:=20=E7=9B=B8=E5=90=8C=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E9=87=8D=E5=A4=8D=E6=9F=A5=E8=AF=A2=E4=B8=8D=E4=BC=9A?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=95=B0=E6=8D=AE(=E5=86=97=E4=BD=99?= =?UTF-8?q?=E7=9A=84=E5=88=A4=E6=96=AD)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/view/search_interface.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/view/search_interface.py b/app/view/search_interface.py index f6da9a3..92516c8 100644 --- a/app/view/search_interface.py +++ b/app/view/search_interface.py @@ -1098,6 +1098,7 @@ def __showSummonerNotFoundMsg(self): parent=self ) + # Fix: 超快速的在候选栏选中两次同样puuid会起两个task加载战绩, 100%干掉客户端 -- By Hpero4 @asyncLockDecorator('loadFirstPageLock') async def searchAndShowFirstPage(self, puuid=None): name = self.searchLineEdit.text() @@ -1108,8 +1109,6 @@ async def searchAndShowFirstPage(self, puuid=None): summoner = await connector.getSummonerByPuuid(name) else: summoner = await connector.getSummonerByName(name) - # Fix: 超快速的在候选栏选中两次同样puuid会起两个task加载战绩, 100%干掉客户端 -- By Hpero4 - puuid = summoner['puuid'] if 'errorCode' in summoner: self.__showSummonerNotFoundMsg()