def add(self, game): """ Add given game. :param game: a NetworkGame object. :type game: diplomacy.client.network_game.NetworkGame """ assert self.game_id == game.game_id if Game.is_player_game(game): if game.role in self.games: raise exceptions.DiplomacyException( 'Power name %s already in game instances set.' % game.role) elif Game.is_observer_game(game): if self.current_observer_type is not None: raise exceptions.DiplomacyException( 'Previous special game %s must be removed before adding new one.' % self.current_observer_type) self.current_observer_type = game.role else: assert Game.is_omniscient_game(game) if self.current_observer_type is not None: raise exceptions.DiplomacyException( 'Previous special game %s must be removed before adding new one.' % self.current_observer_type) self.current_observer_type = game.role self.games[game.role] = game
def start(self, port=None, io_loop=None): """ Start server if not yet started. Raise an exception if server is already started. :param port: (optional) port where server must run. If not provided, try to start on a random selected port. Use property `port` to get current server port. :param io_loop: (optional) tornado IO lopp where server must run. If not provided, get default IO loop instance (tornado.ioloop.IOLoop.instance()). """ if self.backend is not None: raise exceptions.DiplomacyException( 'Server is already running on port %s.' % self.backend.port) if port is None: port = 8432 if io_loop is None: io_loop = tornado.ioloop.IOLoop.instance() handlers = [ tornado.web.url(r"/", ConnectionHandler, {'server': self}), ] settings = { 'cookie_secret': common.generate_token(), 'xsrf_cookies': True, 'websocket_ping_interval': self.ping_seconds, 'websocket_ping_timeout': 2 * self.ping_seconds, 'websocket_max_message_size': 64 * 1024 * 1024 } self.backend = _ServerBackend() self.backend.application = tornado.web.Application( handlers, **settings) self.backend.http_server = self.backend.application.listen(port) self.backend.io_loop = io_loop self.backend.port = port self.set_tasks(io_loop) LOGGER.info('Running on port %d', self.backend.port) if not io_loop.asyncio_loop.is_running(): io_loop.start()
def get_special_token_role(self, token): """ Return role name (either OBSERVER_TYPE or OMNISCIENT_TYPE) for given special token. """ if self.has_omniscient_token(token): return strings.OMNISCIENT_TYPE if self.has_observer_token(token): return strings.OBSERVER_TYPE raise exceptions.DiplomacyException( 'Unknown special token in game %s' % self.game_id)
def handle_response(context, response): """ Call appropriate handler for given response with given request context. :param context: request context. :param response: response received. :return: value returned by handler. """ handler = MAPPING.get(type(context.request), None) if not handler: raise exceptions.DiplomacyException( 'No response handler available for request class %s' % type(context.request).__name__) return handler(context, response)
def __init__(self, **kwargs): self.name = None # type: str # Setting default values kwargs[strings.NAME] = kwargs.get(strings.NAME, None) or self.get_class_name() kwargs[self.id_field] = kwargs.get(self.id_field, None) or str(uuid.uuid4()) if kwargs[strings.NAME] != self.get_class_name(): raise exceptions.DiplomacyException('Expected request name %s, got %s' % (self.get_class_name(), kwargs[strings.NAME])) # Building super(NetworkData, self).__init__(**kwargs)
def sync_done(self): """ Final reconnection work. Remove obsolete game requests and send remaining requests. """ # All sync requests sent have finished. # Remove all obsolete game requests from connection. # A game request is obsolete if it's phase-dependent and if its phase does not match current game phase. request_to_send_updated = {} for context in self.connection.requests_to_send.values( ): # type: RequestFutureContext keep = True if context.request.level == strings.GAME and context.request.phase_dependent: request_phase = context.request.phase server_phase = self.games_phases[context.request.game_id][ context.request.game_role].phase if request_phase != server_phase: # Request is obsolete. context.future.set_exception( exceptions.DiplomacyException( 'Game %s: request %s: request phase %s does not match current server game phase %s.' % (context.request.game_id, context.request.name, request_phase, server_phase))) keep = False if keep: request_to_send_updated[context.request.request_id] = context LOGGER.debug('Keep %d/%d old requests to send.', len(request_to_send_updated), len(self.connection.requests_to_send)) # All requests to send are stored in request_to_send_updated. # Then we can empty connection.requests_to_send. # If we fail to send a request, it will be re-added again. self.connection.requests_to_send.clear() # Send requests. for request_to_send in request_to_send_updated.values( ): # type: RequestFutureContext self.connection.write_request(request_to_send).add_done_callback( _MessageWrittenCallback(request_to_send).callback) # We are reconnected. self.connection.is_reconnecting.set() LOGGER.info('Done reconnection work.')
def reconnect(self): """ Perform concrete reconnection work. """ # Mark all waiting responses as `re-sent` and move them back to responses_to_send. for waiting_context in self.connection.requests_waiting_responses.values( ): # type: RequestFutureContext waiting_context.request.re_sent = True self.connection.requests_to_send.update( self.connection.requests_waiting_responses) self.connection.requests_waiting_responses.clear() # Remove all previous synchronization requests. requests_to_send_updated = {} for context in self.connection.requests_to_send.values( ): # type: RequestFutureContext if isinstance(context.request, requests.Synchronize): context.future.set_exception( exceptions.DiplomacyException( 'Sync request invalidated for game ID %s.' % context.request.game_id)) else: requests_to_send_updated[context.request.request_id] = context self.connection.requests_to_send = requests_to_send_updated # Count games to synchronize. for channel in self.connection.channels.values(): for game_instance_set in channel.game_id_to_instances.values(): for game in game_instance_set.get_games(): self.games_phases.setdefault(game.game_id, {})[game.role] = None self.n_expected_games += 1 if self.n_expected_games: # Synchronize games. for channel in self.connection.channels.values(): for game_instance_set in channel.game_id_to_instances.values(): for game in game_instance_set.get_games(): game.synchronize().add_done_callback( self.generate_sync_callback(game)) else: # No game to sync, finish sync now. self.sync_done()
def handle_notification(connection, notification): """ Call appropriate handler for given notification received by given connection. :param connection: recipient connection. :param notification: received notification. :type connection: diplomacy.Connection :type notification: notifications._AbstractNotification | notifications._GameNotification """ if notification.level == strings.CHANNEL: object_to_notify = connection.channels.get(notification.token, None) else: object_to_notify = _get_game_to_notify(connection, notification) if object_to_notify is None: LOGGER.error('Unknown notification: %s', notification.name) else: handler = MAPPING.get(type(notification), None) if not handler: raise exceptions.DiplomacyException( 'No handler available for notification class %s' % type(notification).__name__) handler(object_to_notify, notification) if notification.level == strings.GAME: object_to_notify.notify(notification)