Skip to content
Open
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
94 changes: 94 additions & 0 deletions client/client/async_core.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import asyncio
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
game_buffer = asyncio.Queue(maxsize=1_000)

log = ClientLogger().log


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):
log.info("Starting client writer loop")
while True:
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
await client_buffer.put(recv.encode())


async def handle_client(reader, writer):
log.info(f"Got connection from {writer.get_extra_info('peername')}")
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):
# 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):
pass


async def handle_game(reader, writer):
pass


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()


async def handle_game_buffer():
pass


async def handle_client_buffer():
pass


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())
84 changes: 71 additions & 13 deletions client/client/gui/client_gui.py
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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
Expand All @@ -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")
Expand Down Expand Up @@ -90,10 +101,49 @@ 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()
raise

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")
Expand All @@ -105,18 +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():
callback = self.write_to_main_window
while True:
self.client.read(output_callback=callback)
sleep(0.01)

Thread(target=output_loop).start()
Thread(target=input_loop).start()
try:
self.read()
sleep(0.01)
except Exception:
raise

try:
Thread(target=output_loop).start()
Thread(target=input_loop).start()
except Exception:
return


if __name__ == "__main__":
Expand Down