async def play(websocket, game, player, connected): """ Receive and process moves from a player. """ async for message in websocket: # Parse a "play" event from the UI. event = json.loads(message) assert event["type"] == "play" column = event["column"] try: # Play the move. row = game.play(player, column) except RuntimeError as exc: # Send an "error" event if the move was illegal. await error(websocket, str(exc)) continue # Send a "play" event to update the UI. event = { "type": "play", "player": player, "column": column, "row": row, } websockets.broadcast(connected, json.dumps(event)) # If move is winning, send a "win" event. if game.winner is not None: event = { "type": "win", "player": game.winner, } websockets.broadcast(connected, json.dumps(event))
def _send_logs_update(self): websockets.broadcast( self._websockets, json.dumps({ "messageType": "logs", "data": self._logs }))
def _send_effects_update(self): websockets.broadcast( self._websockets, json.dumps({ "messageType": "effects", "data": list(self._effects.values()) }))
def run_consumer(shutdown_flag, clients, lock): print("Starting Kafka Consumer.") schema_registry_client = SchemaRegistryClient( {"url": "http://localhost:8081"}) deserializer = AvroDeserializer(schema_registry_client) config = { "bootstrap.servers": "localhost:9092", "group.id": "dashboard-demo", "value.deserializer": deserializer } consumer = DeserializingConsumer(config) consumer.subscribe(["DASHBOARD"]) while not shutdown_flag.done(): msg = consumer.poll(0.2) if msg is None: print("Waiting...") elif msg.error(): print(f"ERROR: {msg.error()}") else: value = msg.value() formatted = simplejson.dumps(value) print(f"Sending {formatted} to {clients}") with lock: websockets.broadcast(clients, formatted) print("Closing Kafka Consumer") consumer.close()
def _send_components_update(self): websockets.broadcast( self._websockets, json.dumps({ "messageType": "components", "data": [asdict(component) for component in self._components.values()] }))
def changed_history(self, history: List[str]): """Update the history and send to clients.""" self._history = history websockets.broadcast( self._websockets, json.dumps({ "messageType": "history", "data": self._history }))
async def leave(player): log(f'player {player.nickname} is leaving') if player.game is not None: if player.game.started: player.move.set_result('_SURRENDER') else: await leave_game(player) players.remove(player) websockets.broadcast( sockets, 'PLAYERS ' + ' '.join(player.nickname for player in players))
async def broadcast(method, size, delay): """Broadcast messages at regular intervals.""" load_average = 0 time_average = 0 pc1, pt1 = time.perf_counter_ns(), time.process_time_ns() await asyncio.sleep(delay) while True: print(f"clients = {len(CLIENTS)}") pc0, pt0 = time.perf_counter_ns(), time.process_time_ns() load_average = 0.9 * load_average + 0.1 * (pt0 - pt1) / (pc0 - pc1) print(f"load = {(pt0 - pt1) / (pc0 - pc1) * 100:.1f}% / " f"average = {load_average * 100:.1f}%, " f"late = {(pc0 - pc1 - delay * 1e9) / 1e6:.1f} ms") pc1, pt1 = pc0, pt0 assert size > 20 message = str(time.time_ns()).encode() + b" " + os.urandom(size - 20) if method == "default": websockets.broadcast(CLIENTS, message) elif method == "naive": # Since the loop can yield control, make a copy of CLIENTS # to avoid: RuntimeError: Set changed size during iteration for websocket in CLIENTS.copy(): await send(websocket, message) elif method == "task": for websocket in CLIENTS: asyncio.create_task(send(websocket, message)) elif method == "wait": if CLIENTS: # asyncio.wait doesn't accept an empty list await asyncio.wait([ asyncio.create_task(send(websocket, message)) for websocket in CLIENTS ]) elif method == "queue": for queue in CLIENTS: queue.put_nowait(message) elif method == "pubsub": PUBSUB.publish(message) else: raise NotImplementedError(f"unsupported method: {method}") pc2 = time.perf_counter_ns() wait = delay + (pc1 - pc2) / 1e9 time_average = 0.9 * time_average + 0.1 * (pc2 - pc1) print(f"broadcast = {(pc2 - pc1) / 1e6:.1f}ms / " f"average = {time_average / 1e6:.1f}ms, " f"wait = {wait * 1e3:.1f}ms") await asyncio.sleep(wait) print()
async def process_events(): """Listen to events in Redis and process them.""" redis = aioredis.from_url("redis://127.0.0.1:6379/1") pubsub = redis.pubsub() await pubsub.subscribe("events") async for message in pubsub.listen(): if message["type"] != "message": continue payload = message["data"].decode() # Broadcast event to all users who have permissions to see it. event = json.loads(payload) recipients = ( websocket for websocket, connection in CONNECTIONS.items() if event["content_type_id"] in connection["content_type_ids"]) websockets.broadcast(recipients, payload)
def log_message(self, level: str, timestamp: float, origin: str, message: str): self._logs.append({ "level": level, "timestamp": timestamp, "origin": origin, "message": message, }) while len(self._logs) > self.MAX_NOF_LOGS: self._logs.pop(0) websockets.broadcast( self._websockets, json.dumps({ "messageType": "log-added", "data": self._logs[-1] }))
async def reconnect(socket, message): player = None try: token = message[10:] filtered_players = [ player for player in players if player.token == token ] if len(filtered_players) == 0: raise MyException('invalid token') player = filtered_players[0] player.socket = socket log(f'player {player.nickname} has reconnected (token: {token})') await socket.send(f'RECONNECT_SUCCESS') websockets.broadcast( sockets, 'PLAYERS ' + ' '.join(player.nickname for player in players)) await socket.send('GAMES ' + ' '.join(game.creator.nickname for game in games)) except MyException as e: await socket.send(f'RECONNECT_ERROR {e}') finally: return player
async def enter(socket, message): player = None try: nickname = message[6:] if not nickname.isalnum(): raise MyException('nickname must be an alpha-numeric string') if nickname in (x.nickname for x in players): raise MyException('nickname already taken') token = generate_token() log(f'new player: {nickname} (token: {token})') player = Player(socket, nickname, token) players.append(player) await socket.send(f'ENTER_SUCCESS {token}') websockets.broadcast( sockets, 'PLAYERS ' + ' '.join(player.nickname for player in players)) await socket.send('GAMES ' + ' '.join(game.creator.nickname for game in games)) except MyException as e: await socket.send(f'ENTER_ERROR {e}') finally: return player
async def leave_game(player): if player.game is None: return log(f'{player.nickname} left {player.game.creator.nickname}\'s game') game = player.game player.game = None if player in game.spectators: game.spectators.remove(player) elif player in game.players: game.players.remove(player) player.is_ready = False if player == game.creator: websockets.broadcast((x.socket for x in game.players), 'GAME_ABANDONED') for player in game.players: player.game = None player.is_ready = False games.remove(game) websockets.broadcast( sockets, 'GAMES ' + ' '.join(game.creator.nickname for game in games)) else: await game.broadcast_state() else: log(f'{player.nickname} not in players nor spectators')
async def counter(websocket, path): try: # Register user USERS.add(websocket) websockets.broadcast(USERS, users_event()) # Send current state to user await websocket.send(state_event()) # Manage state changes async for message in websocket: data = json.loads(message) if data["action"] == "minus": STATE["value"] -= 1 websockets.broadcast(USERS, state_event()) elif data["action"] == "plus": STATE["value"] += 1 websockets.broadcast(USERS, state_event()) else: logging.error("unsupported event: %s", data) finally: # Unregister user USERS.remove(websocket) websockets.broadcast(USERS, users_event())
async def broadcast(self, msg): receivers = set(x for x in self.players if not x.is_fake).union(self.spectators) websockets.broadcast((x.socket for x in receivers), msg)
async def start_game(game): await game.start() games.remove(game) websockets.broadcast( sockets, 'GAMES ' + ' '.join(game.creator.nickname for game in games))
def send_command(self, data) -> None: websockets.broadcast(self._websockets, json.dumps(data))
async def handler(socket, path): log('new connection') sockets.append(socket) try: player = None async for message in socket: if player is None: if message.startswith('ENTER '): player = await enter(socket, message) elif message.startswith('RECONNECT '): player = await reconnect(socket, message) elif message.startswith('_BOT '): token = message[5:] bot, game = next((bot, game) for (bot, game) in bots if bot.token == token) bots.remove((bot, game)) player = Player(socket, bot.nickname, bot.token) player.game = game player.game.players.append(player) player.is_ready = True await player.game.broadcast_state() else: if message == 'LEAVE': await leave(player) player = None break if message.startswith('CREATE_GAME'): log(f'new game (created by {player.nickname})') password = message[12:] game = Game(player, password) games.append(game) player.game = game await game.broadcast_state() websockets.broadcast( sockets, 'GAMES ' + ' '.join(game.creator.nickname for game in games)) elif message.startswith('JOIN_GAME '): await join_game(player, message) elif message.startswith('LEAVE_GAME'): await leave_game(player) elif message.startswith('READY'): if player.game is None: continue player.is_ready = not player.is_ready await player.game.broadcast_state() if len(player.game.players) > 1 and all( x.is_ready for x in player.game.players): asyncio.create_task(start_game(player.game)) elif message.startswith('GAME_OPTIONS '): if player.game is None: continue if player != player.game.creator: continue options = json.loads(message[13:]) player.game.set_options(options) for p in player.game.players: if not 'bot_' in p.nickname: p.is_ready = False await player.game.broadcast_state() elif message.startswith('ADD_BOT '): if player.game is None: continue if player != player.game.creator: continue if len(player.game.players) > 6: continue level = message[8:] protocol = 'wss' if port == 443 else 'ws' ssl_context = ssl.create_default_context( ssl.Purpose.CLIENT_AUTH) if port == 443 else None ws = await websockets.connect( f'{protocol}://localhost:{port}', ssl=ssl_context) bot = spawn_bot(ws, level, player.game) bots.append((bot, player.game)) asyncio.create_task(bot.run()) elif message == 'ROLL': await socket.send('ROLL ' + ' '.join(map(str, player.hidden_dice))) elif message == 'GAME_STATE' and player.game is not None: await socket.send('GAME_STATE ' + player.game.state()) elif message == 'GAME_LOG' and player.game is not None and player.game.started: await socket.send('GAME_LOG ' + player.game.log) elif message.startswith('BID ') or message.startswith( 'REVEAL ') or message.startswith('CHALLENGE'): player.move.set_result(message) except websockets.ConnectionClosed: pass finally: sockets.remove(socket) if player is not None: log(f'lost connection with player {player.nickname}')