From 05235c8df61b0cd6bf588ee15a924576cb10aa0a Mon Sep 17 00:00:00 2001 From: Olivier Gayot Date: Wed, 27 May 2020 20:50:16 +0200 Subject: Don't use coroutines for functions sending notifications Functions which generate notifications for clients were all making use of await and async. This is not great because if we add a notification somewhere, we need to change the function to a coroutine and update all invocations (recursively changing all functions to coroutines). Instead, we now add a task to the event loop whenever a notification needs to be generated. This allows to drop the await and async specifiers from mostly everywhere. On the downside, it means that if we send a notification to n clients, we have to register n tasks. Signed-off-by: Olivier Gayot --- swiftstory/Client.py | 38 ++++++++++++---------- swiftstory/Game.py | 30 +++++++++--------- swiftstory/Player.py | 8 ++--- swiftstory/SwiftStory.py | 82 +++++++++++++++++++++++------------------------- 4 files changed, 80 insertions(+), 78 deletions(-) diff --git a/swiftstory/Client.py b/swiftstory/Client.py index bc347f9..5116f51 100644 --- a/swiftstory/Client.py +++ b/swiftstory/Client.py @@ -1,3 +1,4 @@ +import asyncio import websockets from swiftstory.Status import error @@ -12,7 +13,7 @@ class Client: self.socket = socket self.player = None - async def join_game(self, game_name, lang): + def join_game(self, game_name, lang): if self.game is not None: return error('You are already in a game') @@ -25,27 +26,27 @@ class Client: if game is None: return error('Invalid language') - return await game.try_join(self) + return game.try_join(self) - async def play_white_card(self, card_id): + def play_white_card(self, card_id): if self.game is None: return error('You have to join a game first') - return await self.game.try_play_card(self.player, card_id) + return self.game.try_play_card(self.player, card_id) - async def pick_black_card(self): + def pick_black_card(self): if self.game is None: return error('You have to join a game first') - return await self.game.try_become_judge(self.player) + return self.game.try_become_judge(self.player) - async def collect_cards(self): + def collect_cards(self): if self.game is None: error('You have to join a game first') - return await self.game.try_collect_cards(self.player) + return self.game.try_collect_cards(self.player) - async def designate_card(self, card_id): + def designate_card(self, card_id): if self.game is None: return error('You have to join a game first') - return await self.game.try_designate_card(self.player, card_id) + return self.game.try_designate_card(self.player, card_id) def view_player_cards(self): if self.game is None: @@ -62,12 +63,15 @@ class Client: return error('You have to join a game first') return self.game.try_view_black_card(self.player) - async def send_notification(self, message): - try: - await self.socket.send(message) - except websockets.exceptions.ConnectionClosed: - print("Recipient has disconnected.") + def register_notification(self, message): + async def f(): + try: + await self.socket.send(message) + except websockets.exceptions.ConnectionClosed: + print("Recipient has disconnected.") - async def disconnect(self): + asyncio.create_task(f()) + + def disconnect(self): if self.player is not None: - await self.game.disconnect(self.player) + self.game.disconnect(self.player) diff --git a/swiftstory/Game.py b/swiftstory/Game.py index 1282c0e..93502bd 100644 --- a/swiftstory/Game.py +++ b/swiftstory/Game.py @@ -28,7 +28,7 @@ class Game: random.shuffle(self.board.white_pick) random.shuffle(self.board.black_pick) - async def try_join(self, client): + def try_join(self, client): if len(self.players) >= 10: return error('too many players in this game') @@ -52,7 +52,7 @@ class Game: for p in self.players: if p is not player: - await p.send_notification({'op': 'player_joined_game'}) + p.register_notification({'op': 'player_joined_game'}) cards = [(idx, desc) for idx, (_, desc) in player.cards.items()] @@ -66,7 +66,7 @@ class Game: return success({'cards': cards, 'game_state': state}) - async def try_become_judge(self, player): + def try_become_judge(self, player): if self.state is not self.WAITING_NEW_JUDGE: # TODO what if the judge has quit ? return error('Someone is judge already') @@ -78,12 +78,12 @@ class Game: for p in self.players: if p is not player: - await p.send_notification({'op': 'judge_designed'}) + p.register_notification({'op': 'judge_designed'}) return self.try_view_black_card(player) - async def try_play_card(self, player, card_id): + def try_play_card(self, player, card_id): if self.state is not self.WAITING_COLLECTION: return error('Who asked you to play now ?!') @@ -101,12 +101,12 @@ class Game: self.board.play_card(player, card) - await self.judge.send_notification({'op': 'card_played'}) + self.judge.register_notification({'op': 'card_played'}) return success({'card_id': card_id}) - async def try_collect_cards(self, player): + def try_collect_cards(self, player): if self.state is not self.WAITING_COLLECTION: return error('Do you think it\'s the moment for colletion !?') @@ -120,12 +120,12 @@ class Game: for p in self.players: if p is not player: - await p.send_notification({'op': 'cards_collected'}) + p.register_notification({'op': 'cards_collected'}) return self.try_view_played_cards(player) - async def try_designate_card(self, player, card_id): + def try_designate_card(self, player, card_id): if self.state is not self.WAITING_DESIGNATION: return error('Not now, moron !') @@ -143,7 +143,7 @@ class Game: except IndexError: return error('Invalid card') - await winner.inc_score() + winner.inc_score() # put the cards back in the deck self.board.recycle_played_cards() @@ -153,7 +153,7 @@ class Game: if p.has_played: idx = p.receive_card(self.board.pick_white_card()) - await p.send_notification({ + p.register_notification({ 'op': 'received_card', 'content': { 'card': { @@ -168,7 +168,7 @@ class Game: for p in self.players: if p is not player: - await p.send_notification({'op': 'judge_needed'}) + p.register_notification({'op': 'judge_needed'}) self.state = self.WAITING_NEW_JUDGE @@ -191,19 +191,19 @@ class Game: return error('The black card has not been revealed yet') - async def disconnect(self, player): + def disconnect(self, player): player.client = None if self.judge is player: self.judge = None for p in self.players: - await p.send_notification({'op': 'judge_needed'}) + p.register_notification({'op': 'judge_needed'}) for card, p in self.board.played_cards: p.receive_card(card) - await p.send_notification({ + p.register_notification({ 'op': 'received_card', 'content': { 'card': { diff --git a/swiftstory/Player.py b/swiftstory/Player.py index 786569c..7193b64 100644 --- a/swiftstory/Player.py +++ b/swiftstory/Player.py @@ -17,9 +17,9 @@ class Player: def pop_card(self, card_id): return self.cards.pop(card_id) - async def inc_score(self): + def inc_score(self): self.score += 1 - await self.send_notification({ + self.register_notification({ 'op': 'updated_score', 'content': self.score, }) @@ -29,10 +29,10 @@ class Player: self.next_idx += 1 return self.next_idx - 1 - async def send_notification(self, obj): + def register_notification(self, obj): if self.client is None: return message = json.dumps({'type': 'notification', 'content': obj}) - await self.client.send_notification(message) + self.client.register_notification(message) diff --git a/swiftstory/SwiftStory.py b/swiftstory/SwiftStory.py index b252040..2aa1444 100644 --- a/swiftstory/SwiftStory.py +++ b/swiftstory/SwiftStory.py @@ -14,50 +14,48 @@ from swiftstory.Status import error game_manager = swiftstory.GameManager.GameManager() -async def message_received_handler(client, message): +def message_received_handler(client, message): try: json_msg = json.loads(message) except JSONDecodeError: - res = error('badly formatted json') - else: - op = json_msg['op'] - if op == 'join_game': - try: - game_name = json_msg['game_name'] - except KeyError: - res = error('field `game_name\' is required') - else: - lang = json_msg.get('lang') - res = await client.join_game(game_name, lang) - elif op == 'view_player_cards': - res = client.view_player_cards() - elif op == 'view_black_card': - res = client.view_black_card() - elif op == 'view_played_cards': - res = client.view_played_cards() - elif op == 'pick_black_card': - res = await client.pick_black_card() - elif op == 'designate_card': - card_id = None - try: - card_id = int(json_msg['card_id']) - except (KeyError, TypeError): - pass - finally: - res = await client.designate_card(card_id) - elif op == 'play_white_card': - try: - card_id = int(json_msg['card_id']) - except KeyError: - res = error('field `card_id\' is required') - else: - res = await client.play_white_card(card_id) - elif op == 'collect_cards': - res = await client.collect_cards() + return error('badly formatted json') + + op = json_msg['op'] + if op == 'join_game': + try: + game_name = json_msg['game_name'] + except KeyError: + return error('field `game_name\' is required') else: - res = error('invalid command') - - await client.socket.send(res) + lang = json_msg.get('lang') + return client.join_game(game_name, lang) + elif op == 'view_player_cards': + return client.view_player_cards() + elif op == 'view_black_card': + return client.view_black_card() + elif op == 'view_played_cards': + return client.view_played_cards() + elif op == 'pick_black_card': + return client.pick_black_card() + elif op == 'designate_card': + card_id = None + try: + card_id = int(json_msg['card_id']) + except (KeyError, TypeError): + pass + finally: + return client.designate_card(card_id) + elif op == 'play_white_card': + try: + card_id = int(json_msg['card_id']) + except KeyError: + return error('field `card_id\' is required') + else: + return client.play_white_card(card_id) + elif op == 'collect_cards': + return client.collect_cards() + else: + return error('invalid command') async def swiftstory(websocket, path): @@ -65,9 +63,9 @@ async def swiftstory(websocket, path): with contextlib.suppress(websockets.exceptions.ConnectionClosed): async for message in client.socket: - await message_received_handler(client, message) + await client.socket.send(message_received_handler(client, message)) - await client.disconnect() + client.disconnect() def main(): -- cgit v1.2.3