GUI插件网络消息移到线程处理

This commit is contained in:
John Smith 2024-03-17 12:19:40 +08:00
parent 644e36b4eb
commit 0b39380f70
4 changed files with 139 additions and 102 deletions

View File

@ -6,6 +6,7 @@ import logging
from typing import * from typing import *
import pubsub.pub as pub import pubsub.pub as pub
import wx
import blcsdk import blcsdk
import blcsdk.models as sdk_models import blcsdk.models as sdk_models
@ -26,7 +27,7 @@ async def init():
blc_rooms = await blcsdk.get_rooms() blc_rooms = await blcsdk.get_rooms()
for blc_room in blc_rooms: for blc_room in blc_rooms:
if blc_room.room_id is not None: if blc_room.room_id is not None:
_get_or_add_room(blc_room.room_key, blc_room.room_id) wx.CallAfter(_get_or_add_room, blc_room.room_key, blc_room.room_id)
except blcsdk.SdkError: except blcsdk.SdkError:
pass pass
@ -38,7 +39,10 @@ def shut_down():
class MsgHandler(blcsdk.BaseHandler): class MsgHandler(blcsdk.BaseHandler):
def on_client_stopped(self, client: blcsdk.BlcPluginClient, exception: Optional[Exception]): def on_client_stopped(self, client: blcsdk.BlcPluginClient, exception: Optional[Exception]):
logger.info('blivechat disconnected') logger.info('blivechat disconnected')
__main__.start_shut_down() wx.CallAfter(__main__.start_shut_down)
def handle(self, client: blcsdk.BlcPluginClient, command: dict):
wx.CallAfter(super().handle, client, command)
def _on_open_plugin_admin_ui( def _on_open_plugin_admin_ui(
self, client: blcsdk.BlcPluginClient, message: sdk_models.OpenPluginAdminUiMsg, extra: sdk_models.ExtraData self, client: blcsdk.BlcPluginClient, message: sdk_models.OpenPluginAdminUiMsg, extra: sdk_models.ExtraData

View File

@ -1,62 +1,53 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import asyncio import asyncio
import concurrent.futures
import logging
import logging.handlers import logging.handlers
import os import os
import signal import signal
import sys import threading
from typing import *
import pubsub.pub as pub
import wx import wx
import blcsdk import blcsdk
import blcsdk.models as sdk_models
import config import config
import listener import listener
import ui.app import ui.room_config_dialog
import ui.room_frame
import ui.task_bar_icon
logger = logging.getLogger('native-ui') logger = logging.getLogger('native-ui')
app: Optional['App'] = None
async def main():
try:
await init()
await run()
finally:
await shut_down()
return 0
async def init(): def main():
init_signal_handlers() init_signal_handlers()
init_logging() init_logging()
config.init() config.init()
await blcsdk.init() global app
if not blcsdk.is_sdk_version_compatible(): app = App()
raise RuntimeError('SDK version is not compatible')
ui.app.init() logger.info('Running event loop')
await listener.init() app.MainLoop()
def init_signal_handlers(): def init_signal_handlers():
signums = (signal.SIGINT, signal.SIGTERM) def signal_handler(*_args):
try: wx.CallAfter(start_shut_down)
loop = asyncio.get_running_loop()
for signum in signums:
loop.add_signal_handler(signum, start_shut_down)
except NotImplementedError:
def signal_handler(*args):
asyncio.get_running_loop().call_soon(start_shut_down, *args)
# 不太安全但Windows只能用这个 for signum in (signal.SIGINT, signal.SIGTERM):
for signum in signums: signal.signal(signum, signal_handler)
signal.signal(signum, signal_handler)
def start_shut_down(*_args): def start_shut_down():
app = wx.GetApp() if app is not None and app.IsMainLoopRunning():
if app is not None:
app.ExitMainLoop() app.ExitMainLoop()
else: else:
wx.Exit() wx.Exit()
@ -77,16 +68,120 @@ def init_logging():
) )
async def run(): class App(wx.App):
logger.info('Running event loop') def __init__(self, *args, **kwargs):
await wx.GetApp().MainLoop() self._network_worker = NetworkWorker()
logger.info('Start to shut down')
self._dummy_timer: Optional[wx.Timer] = None
self._task_bar_icon: Optional[ui.task_bar_icon.TaskBarIcon] = None
self._key_room_frame_dict: Dict[sdk_models.RoomKey, ui.room_frame.RoomFrame] = {}
self._room_config_dialog: Optional[ui.room_config_dialog.RoomConfigDialog] = None
super().__init__(*args, clearSigInt=False, **kwargs)
self.SetExitOnFrameDelete(False)
def OnInit(self):
# 这个定时器只是为了及时响应信号因为只有处理UI事件时才会唤醒主线程
self._dummy_timer = wx.Timer(self)
self._dummy_timer.Start(1000)
self.Bind(wx.EVT_TIMER, lambda _event: None, self._dummy_timer)
self._task_bar_icon = ui.task_bar_icon.TaskBarIcon()
pub.subscribe(self._on_add_room, 'add_room')
pub.subscribe(self._on_del_room, 'del_room')
pub.subscribe(self._on_room_frame_close, 'room_frame_close')
pub.subscribe(self._on_add_room, 'open_room')
pub.subscribe(self._on_open_room_config_dialog, 'open_room_config_dialog')
self._network_worker.init()
return True
def OnExit(self):
logger.info('Start to shut down')
self._network_worker.start_shut_down()
self._network_worker.join(10)
return super().OnExit()
def _on_add_room(self, room_key: sdk_models.RoomKey):
if room_key in self._key_room_frame_dict:
return
room_frame = self._key_room_frame_dict[room_key] = ui.room_frame.RoomFrame(None, room_key)
room_frame.Show()
def _on_del_room(self, room_key: sdk_models.RoomKey):
room_frame = self._key_room_frame_dict.pop(room_key, None)
if room_frame is not None:
room_frame.Close(True)
def _on_room_frame_close(self, room_key: sdk_models.RoomKey):
self._key_room_frame_dict.pop(room_key, None)
def _on_open_room_config_dialog(self):
if self._room_config_dialog is None or self._room_config_dialog.IsBeingDeleted():
self._room_config_dialog = ui.room_config_dialog.RoomConfigDialog(None)
self._room_config_dialog.Show()
async def shut_down(): class NetworkWorker:
listener.shut_down() def __init__(self):
await blcsdk.shut_down() self._worker_thread = threading.Thread(
target=asyncio.run, args=(self._worker_thread_func(),), daemon=True
)
self._thread_init_future = concurrent.futures.Future()
self._loop: Optional[asyncio.AbstractEventLoop] = None
self._shut_down_event: Optional[asyncio.Event] = None
def init(self):
self._worker_thread.start()
self._thread_init_future.result(10)
def start_shut_down(self):
if self._shut_down_event is not None:
self._loop.call_soon_threadsafe(self._shut_down_event.set)
def join(self, timeout=None):
self._worker_thread.join(timeout)
return not self._worker_thread.is_alive()
async def _worker_thread_func(self):
self._loop = asyncio.get_running_loop()
try:
try:
await self._init_in_worker_thread()
self._thread_init_future.set_result(None)
except BaseException as e:
self._thread_init_future.set_exception(e)
return
await self._run()
finally:
await self._shut_down()
async def _init_in_worker_thread(self):
await blcsdk.init()
if not blcsdk.is_sdk_version_compatible():
raise RuntimeError('SDK version is not compatible')
await listener.init()
self._shut_down_event = asyncio.Event()
async def _run(self):
logger.info('Running network thread event loop')
await self._shut_down_event.wait()
logger.info('Network thread start to shut down')
@staticmethod
async def _shut_down():
listener.shut_down()
await blcsdk.shut_down()
if __name__ == '__main__': if __name__ == '__main__':
sys.exit(asyncio.run(main())) main()

View File

@ -1,4 +1,3 @@
PyPubSub==4.0.3 PyPubSub==4.0.3
wxasync==0.49
wxPython==4.2.1 wxPython==4.2.1
XlsxWriter==3.2.0 XlsxWriter==3.2.0

View File

@ -1,61 +0,0 @@
# -*- coding: utf-8 -*-
import logging
from typing import *
import pubsub.pub as pub
import wxasync
import blcsdk.models as sdk_models
import ui.room_config_dialog
import ui.room_frame
import ui.task_bar_icon
logger = logging.getLogger('native-ui.' + __name__)
_app: Optional['App'] = None
def init():
global _app
_app = App()
class App(wxasync.WxAsyncApp):
def __init__(self, *args, **kwargs):
self._task_bar_icon: Optional[ui.task_bar_icon.TaskBarIcon] = None
super().__init__(*args, clearSigInt=False, **kwargs)
self.SetExitOnFrameDelete(False)
self._key_room_frame_dict: Dict[sdk_models.RoomKey, ui.room_frame.RoomFrame] = {}
self._room_config_dialog: Optional[ui.room_config_dialog.RoomConfigDialog] = None
def OnInit(self):
self._task_bar_icon = ui.task_bar_icon.TaskBarIcon()
pub.subscribe(self._on_add_room, 'add_room')
pub.subscribe(self._on_del_room, 'del_room')
pub.subscribe(self._on_room_frame_close, 'room_frame_close')
pub.subscribe(self._on_add_room, 'open_room')
pub.subscribe(self._on_open_room_config_dialog, 'open_room_config_dialog')
return True
def _on_add_room(self, room_key: sdk_models.RoomKey):
if room_key in self._key_room_frame_dict:
return
room_frame = self._key_room_frame_dict[room_key] = ui.room_frame.RoomFrame(None, room_key)
room_frame.Show()
def _on_del_room(self, room_key: sdk_models.RoomKey):
room_frame = self._key_room_frame_dict.pop(room_key, None)
if room_frame is not None:
room_frame.Close(True)
def _on_room_frame_close(self, room_key: sdk_models.RoomKey):
self._key_room_frame_dict.pop(room_key, None)
def _on_open_room_config_dialog(self):
if self._room_config_dialog is None or self._room_config_dialog.IsBeingDeleted():
self._room_config_dialog = ui.room_config_dialog.RoomConfigDialog(None)
self._room_config_dialog.Show()