Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FIX: 特定条件下, 战绩查询会干掉客户端 #450

Merged
merged 5 commits into from
Aug 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions app/components/search_line_edit.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
9 changes: 8 additions & 1 deletion app/lol/connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import json
import threading
import re
from asyncio import CancelledError
from collections import deque

import requests
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -324,7 +331,7 @@ def __initFolder(self):
"profile icons",
"rune icons",
"summoner spell icons",
"augment icons"
"augment icons",
"splashes",
]:
p = f"app/resource/game/{folder}"
Expand Down
34 changes: 30 additions & 4 deletions app/view/search_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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)
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -1077,6 +1098,8 @@ def __showSummonerNotFoundMsg(self):
parent=self
)

# Fix: 超快速的在候选栏选中两次同样puuid会起两个task加载战绩, 100%干掉客户端 -- By Hpero4
@asyncLockDecorator('loadFirstPageLock')
async def searchAndShowFirstPage(self, puuid=None):
name = self.searchLineEdit.text()
if name == "":
Expand Down Expand Up @@ -1105,9 +1128,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()
Expand All @@ -1132,7 +1153,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))

Expand Down Expand Up @@ -1188,12 +1208,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:
# 为加载战绩详情让行
Expand All @@ -1205,6 +1228,7 @@ async def __loadGames(self, puuid):
):
await asyncio.sleep(.2)

t1 = time.time()
try:
games = await connector.getSummonerGamesByPuuidSlowly(
puuid, begIdx, endIdx)
Expand All @@ -1213,7 +1237,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:
Expand Down
Loading