From 0855c2d2846381b2111b6695b2a341667198d583 Mon Sep 17 00:00:00 2001 From: Jonas Andersson Date: Fri, 23 Jul 2021 01:08:53 +0200 Subject: [PATCH 1/2] Work in progress for asyncio based core --- client/client/async_core.py | 69 +++++++++++++++++++++++++++++++++ client/client/gui/client_gui.py | 60 +++++++++++++++++++++++++--- 2 files changed, 123 insertions(+), 6 deletions(-) create mode 100644 client/client/async_core.py diff --git a/client/client/async_core.py b/client/client/async_core.py new file mode 100644 index 0000000..029531f --- /dev/null +++ b/client/client/async_core.py @@ -0,0 +1,69 @@ +import asyncio +import random # just for testing +from client.client_logger import ClientLogger + +# The client will store messages in the client buffer until the write_to_game task picks them up +client_buffer = asyncio.Queue(maxsize=100) +# The game buffer will hold messages from the game socket until the write_to_client task picks them up +game_buffer = asyncio.Queue(maxsize=1_000) + +log = ClientLogger().log + + +def connect_to_game(): + pass + + +async def write_to_client(writer): + # Just mocking a connection to try this out + random_messages = ["asdf\n", "yada\n"] + while True: + message = random.choice(random_messages).encode("ASCII") + writer.write(message) + log.info(f"Sending to client: {message}") + asyncio.create_task(writer.drain()) + await asyncio.sleep(10) + + +async def read_from_client(reader): + while True: + recv = await reader.readline() + log.info(f"Received: {recv}") + if not recv: + return + asyncio.create_task(client_buffer.put(recv.encode())) + + +async def handle_client(reader, writer): + log.info(f"Got connection from {writer.get_extra_info('peername')}") + asyncio.create_task(write_to_client(writer)) + asyncio.create_task(read_from_client(reader)) + + +async def read_from_game(reader): + pass + + +async def write_to_game(writer): + pass + + +async def handle_game(reader, writer): + pass + + +async def listen_for_client(): + client_server = await asyncio.start_server(handle_client, "127.0.0.1", 10002) + async with client_server: + await client_server.serve_forever() + + +def handle_game_buffer(): + pass + + +def handle_client_buffer(): + pass + + +asyncio.run(listen_for_client()) diff --git a/client/client/gui/client_gui.py b/client/client/gui/client_gui.py index 3bac85a..d8137fc 100644 --- a/client/client/gui/client_gui.py +++ b/client/client/gui/client_gui.py @@ -1,7 +1,12 @@ import sys + from threading import Thread from time import sleep +# from PyQt6.QtCore import QThread +from telnetlib import Telnet +import logging + from PyQt6.QtWidgets import ( QApplication, QDockWidget, @@ -13,7 +18,7 @@ from PyQt6.QtGui import QIcon, QTextCursor, QAction from PyQt6.QtCore import Qt -from client.core import Engine +# from client.core import Engine from client.client_logger import ClientLogger # TODO: Lock the scrollbar when its not all the way at the bottom @@ -27,10 +32,16 @@ def __init__(self): self.input_buffer = [] self.status_bar = self.statusBar() self.input_dock = QDockWidget() - self.client = Engine() + self.connection = Telnet() + # self.client = Engine() self.__init_ui() - self.client.connect() + self._connect_to_async_engine() + # self.client.connect() self.gui_reactor() + # self.gui_reactor_selector() + + def _connect_to_async_engine(self): + self.connection.open(host="localhost", port=10002) def __init_ui(self): self.log.debug("Initializing UI") @@ -90,10 +101,48 @@ def write_to_main_window(self, text: str): def write(self, write_data: str): write_data = write_data + "\n" - self.client.connection.write(write_data.encode("ASCII")) + self.connection.write(write_data.encode("ASCII")) self.write_to_main_window(f">{write_data}") self.input.clear() + def disconnect(self): + goodbye = """ + ****************** + *****THE END****** + ****************** + + """.split( + "\n" + ) + for line in goodbye: + self.write_to_main_window(line) + self.log.info("Connection closed") + if self.connection: + self.connection.close() + + def read(self): + if not self.connection: + self.disconnect() + + try: + read_data = self.connection.read_very_eager().decode("ASCII") + except EOFError: + self.disconnect() + + for line in read_data.split("\n"): + + # TODO: This if might be redundant + if line: + logging.getLogger("game").info(line) + # try: + # XMLParser(target=self.xml_data).feed(line) + # except ParseError: + # pass + # line = self.xml_data.strip(line) + if not line: + continue + self.write_to_main_window(line) + def contextMenuEvent(self, event): context_menu = QMenu(self) exit_action = context_menu.addAction("Quit") @@ -110,9 +159,8 @@ def input_loop(): sleep(0.01) def output_loop(): - callback = self.write_to_main_window while True: - self.client.read(output_callback=callback) + self.read() sleep(0.01) Thread(target=output_loop).start() From 8afcfd282752354bd0b3b2768b0543dbe8f4e491 Mon Sep 17 00:00:00 2001 From: Jonas Andersson Date: Fri, 23 Jul 2021 16:38:50 +0200 Subject: [PATCH 2/2] f --- client/client/async_core.py | 57 ++++++++++++++++++++++++--------- client/client/gui/client_gui.py | 24 ++++++++++---- 2 files changed, 58 insertions(+), 23 deletions(-) diff --git a/client/client/async_core.py b/client/client/async_core.py index 029531f..aaaeade 100644 --- a/client/client/async_core.py +++ b/client/client/async_core.py @@ -2,6 +2,10 @@ import random # just for testing from client.client_logger import ClientLogger +# Order of operations +# Start the client listener and connect to the game +# When the client connects +test_game_messages = ["asdf\n", "yada\n", "waza\n", "yerp\n"] # The client will store messages in the client buffer until the write_to_game task picks them up client_buffer = asyncio.Queue(maxsize=100) # The game buffer will hold messages from the game socket until the write_to_client task picks them up @@ -10,38 +14,47 @@ log = ClientLogger().log -def connect_to_game(): - pass +async def connect_to_game(): + log.info("Connecting to game") + await client_buffer.put("Connecting to game") + asyncio.create_task(read_from_game(None)) async def write_to_client(writer): - # Just mocking a connection to try this out - random_messages = ["asdf\n", "yada\n"] + log.info("Starting client writer loop") while True: - message = random.choice(random_messages).encode("ASCII") - writer.write(message) - log.info(f"Sending to client: {message}") - asyncio.create_task(writer.drain()) - await asyncio.sleep(10) + if not game_buffer.empty(): + # Queue.get blocks until it gets an item + message = await game_buffer.get() + log.info(f"Sending to client: {message}") + writer.write(message) + asyncio.create_task(writer.drain()) async def read_from_client(reader): + log.info("Starting client reader loop") while True: recv = await reader.readline() log.info(f"Received: {recv}") if not recv: return - asyncio.create_task(client_buffer.put(recv.encode())) + await client_buffer.put(recv.encode()) async def handle_client(reader, writer): log.info(f"Got connection from {writer.get_extra_info('peername')}") - asyncio.create_task(write_to_client(writer)) - asyncio.create_task(read_from_client(reader)) + write_task = asyncio.create_task(write_to_client(writer)) + read_task = asyncio.create_task(read_from_client(reader)) + # Producer and consumer need to start in parallel: https://stackoverflow.com/questions/56377402/why-is-asyncio-queue-await-get-blocking + await asyncio.gather(write_task, read_task) async def read_from_game(reader): - pass + # Placeholder test that adds a message to the game buffer every 10 seconds + while True: + message = random.choice(test_game_messages).encode("ASCII") + await game_buffer.put(message) + await asyncio.sleep(10) async def write_to_game(writer): @@ -53,17 +66,29 @@ async def handle_game(reader, writer): async def listen_for_client(): + log.info("Listening for front-end/client") client_server = await asyncio.start_server(handle_client, "127.0.0.1", 10002) async with client_server: await client_server.serve_forever() -def handle_game_buffer(): +async def handle_game_buffer(): pass -def handle_client_buffer(): +async def handle_client_buffer(): pass -asyncio.run(listen_for_client()) +async def main(): + try: + client_task = asyncio.create_task(listen_for_client()) + game_task = asyncio.create_task(connect_to_game()) + await asyncio.gather(client_task, game_task) + except Exception: + client_task.cancel() + game_task.cancel() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/client/client/gui/client_gui.py b/client/client/gui/client_gui.py index d8137fc..1c3e4bb 100644 --- a/client/client/gui/client_gui.py +++ b/client/client/gui/client_gui.py @@ -128,6 +128,7 @@ def read(self): read_data = self.connection.read_very_eager().decode("ASCII") except EOFError: self.disconnect() + raise for line in read_data.split("\n"): @@ -154,17 +155,26 @@ def contextMenuEvent(self, event): def gui_reactor(self): def input_loop(): while True: - if self.input_buffer: - self.write(self.input_buffer.pop(0)) - sleep(0.01) + try: + if self.input_buffer: + self.write(self.input_buffer.pop(0)) + sleep(0.01) + except Exception: + raise def output_loop(): while True: - self.read() - sleep(0.01) + try: + self.read() + sleep(0.01) + except Exception: + raise - Thread(target=output_loop).start() - Thread(target=input_loop).start() + try: + Thread(target=output_loop).start() + Thread(target=input_loop).start() + except Exception: + return if __name__ == "__main__":