def __run__(cls, *args, **kwargs): Globals.access_token = Access().register_access( "SUPER INTERNAL ACCESS", AccessLevel.Internal) Globals.service_name = cls.__name__ + CommandLine.get_arguments( ).postfix service_instance = cls(*args, **kwargs) Globals.this_service = service_instance signal.signal(signal.SIGTERM, service_instance.sigterm_handler) # signal.signal(signal.SIGBREAK, service_instance.sigterm_handler) signal.signal(signal.SIGINT, service_instance.sigterm_handler) try: asyncio.get_event_loop().run_until_complete( service_instance) # this calls __await__ asyncio.get_event_loop().run_until_complete( service_instance.done()) asyncio.get_event_loop().run_forever() except KeyboardInterrupt: INFO_MSG("Preparing to shut down service...") except Exception as exc: from traceback import print_exc ERROR_MSG("Exception raised:", exc) print_exc() INFO_MSG("Press enter to continue") input() print("What's your name?\n> ", end="") name = input() print(f"{name}, how did you come to this?\n> ", end="") reason = input() WARN_MSG( f'The program has been stopped, the {name} provoked an error, and says: "{reason}". Dismiss him' )
def terminate(self): INFO_MSG(f"Service {self.get_class_name()} going to be stopped") def on_terminate(proc): process_info = self.processes.get(proc.pid) status = proc.status() WARN_MSG( "Process did't terminated after timeout. pid: %s, status: %s, info: %s", proc.pid, status, process_info) processes = [] for pid, info in self.processes.items(): process = psutil.Process(pid) processes.append(process) process.terminate() INFO_MSG( f"Service {self.get_class_name()}. Send terminate to {info['name']}, status: {process.status()}, pid_exists: {psutil.pid_exists(pid)}" ) if processes: try: from time import sleep # sleep(5) # for i in processes: # INFO_MSG(f"1 Service {self.get_class_name()}. -- {i.pid} --, status: {i.status()}, pid_exists: {psutil.pid_exists(i.pid)}") gone, alive = psutil.wait_procs([ i for i in processes if i.status() != psutil.STATUS_ZOMBIE ], timeout=60, callback=on_terminate) # todo: Some strange sings here except Exception as e: WARN_MSG(f"Service {self.get_class_name()} exception {e}") INFO_MSG(f"Service {self.get_class_name()} is stopped") sys.exit(0)
async def request_game_for_player(self, game_type, player_session, extra=None): """ Запрос быстрого матча для игрока @param player_session: сессия игрока @param extra: дополнительные опции @return: """ INFO_MSG(f"Requesting game with session {player_session}") if player_session in self.players_games_mapping: if self.players_games_mapping[player_session].return_back_player(player_session): return EReqGameResponse.Success try: game = await self.request_game_info(game_type, extra) except MatchMakingError: return EReqGameResponse.Denied if game and not self.is_session_already_requested(player_session): result = await game.enqueue_player(player_session) self.lock_player_for_game(player_session, game) return result else: INFO_MSG(f"Cannot request new game: game={game}, " f"already requested? {self.is_session_already_requested(player_session)}") return EReqGameResponse.Denied
async def wake_up_dedicated_server_for_match(self): """ Поднять сервер для этого матча """ INFO_MSG(f"Waking up dedicated server for match {self}") map_info: FMapInfo = Maps.get_by("Name", self.map_name) if not map_info: return ERROR_MSG(f"Missing map {self.map_name}") port = self.match_making.new_dedicated_server_port() map_parameters = f"{ map_info.Asset }?listen&port={ port }&game={ get_uclass_true_path(map_info.GameMode) }" kw_params = dict(port=str(port), dedic_id=self.id, MaxPlayers=map_info.PlayersMax, BotsCount=map_info.PlayersMax) # MaxPlayers is BotsCount base_ip, base_port = self.service.endpoint self.dedicated_server = await self.base.supervisor.RequestDedicatedServer( map_parameters, self.service.exposed_ip, base_port, {}, kw_params) await self.dedicated_server.PrepareMatch() INFO_MSG(f"Dedicated server waked up for match {self}") self.dedicated_server.add_lost_callback( lambda: self.on_dedicated_server_dropped(self.dedicated_server)) self.port = port self.ip = Globals.this_service.exposed_ip return self.dedicated_server
async def enqueue_player(self, session): """ Добавить в очередь игрока Запускает матч, если список игроков заполнен @param session: сессия игрока @param character_id: ИД персонажа """ INFO_MSG(f"Enqueueing player {session}") request_result = EReqGameResponse.Wait if self.fulfilled: WARN_MSG(f"Can't enqueue {session}. Game is fulfilled") return EReqGameResponse.Denied self.players[session] = GamePlayerInfo(session=session) session.ue4client.add_lost_callback( partial(self.on_client_dropped, session)) # Начать выполнение матча. Можно только если команда заполнена и матч на данный момент не выполняется if self.fulfilled and not self.performing: asyncio.Task(self.perform()) # Допустить игрока (дополнительного игрока, для восполнения) в матч (можно только если уже начат) if self.started: await self.accept_player_to_started_game(session, self.players[session]) request_result = EReqGameResponse.Success return request_result
async def __aenter__(self): while any([ getattr(context, varname).locked for context, varname in self.vars ]): for context, varname in self.vars: var = getattr(context, varname) if var.locked: # WARN_MSG("Variable %s locked, transaction %s waiting for unlock..." % (varname, self)) try: await asyncio.wait_for(var.waitforunlock(), 2) except asyncio.TimeoutError: ERROR_MSG( "Variable %s locked by %s more for 5 seconds! Check your code for nested transactions with the same variables" % (varname, var.locker), depth=1) # raise TransactionError("Variables locked too long") INFO_MSG("Entered %s with %s" % (self, (self.vars, )), depth=1) for context, varname in self.vars: var = getattr(context, varname) await var.lock(self) self.savepoint() return Replication.__enter__(self)
async def __ainit__(self, match_making, id=0, ip="0.0.0.0", port=0, dedicated_server=None, players=None, map_name=None, started=False, max_players=0, teams_assigned=False): await GameBase.__ainit__(self, match_making, id, max_players, started=started, players=players, teams_assigned=teams_assigned) await Entity.__ainit__(self) self.ip = ip # ip dedicated server'а self.port = port # порт вступления в dedicated server self.dedicated_server: 'UE4App' = dedicated_server # мейлбокс dedicated server'а self.map_name = map_name # имя выбранной карты self.state = EMatchState.Idle # состояние матча self.on_game_done = None self.empty_match_timer_handle = None INFO_MSG(f"Created and prepared new match {self}")
async def perform(self): """ Запуск текущего матча """ INFO_MSG(f"Performing match {self}") if self.dedicated_server: return \ ERROR_MSG(f"The match {self} already performing") self.performing = True self.update_state(EMatchState.Preparing) try: await self.wake_up_dedicated_server_for_match() except WakeupError: self.rollback() raise MatchMakingError( "Unable to wakeup dedicated server for match") await self.dedicated_server.SetupGame(self.max_players) self.assign_teams() self.started = True for player_info in self.players.values(): await self.register_player_info(player_info) self.update_state(EMatchState.InGame) self.dedicated_server.MatchStart(self.id) self.handle_start() for session, player_info in self.players.items(): player_info.join(self.ip, self.port)
async def async_set_value(self, value): INFO_MSG("saving", self.owner_dbid, self.owner_class_name, self.owner_property_name, self.prop_info.prop_type.get_type_name()) from Core.BaseEntity import BaseEntity if isinstance(value, BaseEntity): from Core.LocalDatatypes import int32 await self.db.UpdateEntityVariable(self.owner_dbid, self.owner_class_name, self.owner_property_name, 'int32', int32(value.dbid).serialize()) else: await self.db.UpdateEntityVariable(self.owner_dbid, self.owner_class_name, self.owner_property_name, self.prop_info.prop_type.get_type_name(), value.serialize())
async def __ainit__(self, dbid, username): await super().__ainit__(dbid) self.username = self.Username = username self.client = None self.checker_time = None self.access_token = "" self.login_date = datetime.datetime.now() self.currently_in_game = False INFO_MSG("Hi!")
def handle_empty_match(self): INFO_MSG( f"Closing and exiting game {self} [second chance (todo)]") # todo self.empty_match_timer_handle = None if not len(self.players): self.close_game() if self.dedicated_server.dedicated_server: asyncio.Task(self.dedicated_server.ExecuteCode("quit")) self.match_making.destroy_game(self)
async def subscribe_connection(self, endpoint_or_connection_or_mailbox): INFO_MSG("Subscribing") endpoint_or_connection = endpoint_or_connection_or_mailbox if isinstance(endpoint_or_connection_or_mailbox, Mailbox): endpoint_or_connection = endpoint_or_connection_or_mailbox.client_connection client_connection, _ = await self.get_connection_and_endpoint_with_lost_callback( endpoint_or_connection) self.clients_connections.append(client_connection)
def request_player_exit_from_game(self, session): """ Запрос выхода игрока из матча @param session: сессия игрока """ INFO_MSG(f"{session}") for games in self.games.values(): for game in games: if game.conditional_dequeue_player(session): self.free_player(session)
def done_game(self, game_id, winners, winner_team_id): """ Конец матча @param game_id: идентификатор игры @param winners: победители """ INFO_MSG(f"Done game id={game_id}, winners={winners}, winner_team_id={winner_team_id}") for games in self.games.values(): for game in games: INFO_MSG(f"game: {game}") if game.id == game_id: asyncio.Task(game.done(winners, winner_team_id)) games.remove(game) for player_session in game.players: self.free_player(player_session) break if game.is_subgame_id(game_id): asyncio.Task(game.done_subgame(winners, winner_team_id)) break
def work(city, building_name: str, citizens_ids: List[int32], ability_type_name: str, amount: int = 1, **kwargs) -> WorkingResult: result = WorkingResult() if city.Citizens.locked or amount == 0: citizens_by_abilities = get_citizens_with_ability_names_by_ids_and_ability_type( city, citizens_ids, ability_type_name, building_name) for ability_name, citizens in citizens_by_abilities.items(): ability_info = CitizenAbilities.get_by("Name", ability_name) for citizen in citizens: current_experince = citizen.Abilities[ability_name] multiplier = ability_info['ExperienceMultiplier'] ability_level = get_ability_level(current_experince, multiplier) leveled_ability_info = CitizenAbilitiesLeveling.get_by( "Level", ability_level) parameters = deepcopy(ability_info['BaseParameters']) if leveled_ability_info: for parameter_name in parameters.keys(): if parameter_name in ability_info[ 'BaseParameters']: if isinstance(parameters[parameter_name], (int, float)): parameters[ parameter_name] += leveled_ability_info[ 'Parameters'][ parameter_name] * ability_info[ 'LeveledParametersMultipliers'][ parameter_name] else: if parameter_name in leveled_ability_info[ 'Parameters']: parameters[ parameter_name] = leveled_ability_info[ 'Parameters'][parameter_name] func = getattr(CityCitizens, ability_type_name, None) if func is not None: INFO_MSG( f"Working {ability_type_name} in {building_name} with citizen {citizen}" ) try: func(result, **parameters, **kwargs) except Exception as e: ERROR_MSG( f"Something went wrong in CityCitizens::{ability_type_name}" ) print_exc() else: ERROR_MSG(f"Ability {ability_type_name} not released") if amount > 0: citizen.Abilities[ability_name] += amount return result
async def empty_database(self): INFO_MSG("Clear database") await self.driver.exec_raw(""" DROP TABLE IF EXISTS public.users; DROP TABLE IF EXISTS public.entities; DROP TABLE IF EXISTS public.classes; DROP TABLE IF EXISTS public.users_uncofirmed; DROP TABLE IF EXISTS public.types; DROP TABLE IF EXISTS public.statistics; --- DROP TABLE IF EXISTS public.storages; """)
async def enqueue_player(self, session): result = await super().enqueue_player(session) if result != EReqGameResponse.Denied and not self.timeout_timer and len( self.players) >= SURVIVAL_MIN_PLAYERS: INFO_MSG("Timeout started") if not self.fulfilled and not self.performing and not self.started: self.timeout_timer = self.call_later(self.perform, SURVIVAL_TIMEOUT) return result
async def get_connection_and_endpoint_with_lost_callback( self, endpoint_or_connection): INFO_MSG("endpoint_or_connection") if isinstance(endpoint_or_connection, TCPClient): endpoint = endpoint_or_connection.endpoint client_connection = endpoint_or_connection client_connection.add_lost_callback(self.on_connection_lost) else: endpoint = endpoint_or_connection client_connection = await self.service.create_client_connection( tuple(endpoint_or_connection), on_lost=self.on_connection_lost) return client_connection, endpoint
async def __aexit__(self, exc_type, exc_val, exc_tb): do_not_raise_exc = True if exc_type is None: new_variables = TArray[FPropertyInfo]() for context, varname in self.vars: var = getattr(context, varname) info = FPropertyInfo(EntityDBID=context.dbid, EntityClass=context.__class__.__name__, PropertyName=var.property_name, PropertyTypeName=var.get_type_name(), SerializedValue=var.serialize()) new_variables.append(info) success = await Globals.this_service.db.UpdateVariablesTransactionally( self.old_variables, new_variables) if success: self.unlock_all() INFO_MSG("%s done" % self, depth=1) Replication.__exit__(self, exc_type, exc_val, exc_tb) else: self.rollback("DB error") self.unlock_all() ERROR_MSG( "%s failed, because values in database not actual. Variables rolled back" % self, depth=1) raise TransactionError("Variables changed outside transaction") elif exc_type == TransactionExitException: self.rollback("Interrupted") self.unlock_all() do_not_raise_exc = True WARN_MSG( "Transaction %s interrupted from code. Variables rolled back" % self, depth=1) else: self.rollback("Code error") self.unlock_all() do_not_raise_exc = False ERROR_MSG( "%s failed, because errors in code. Variables rolled back" % self, depth=1) # for context, varname in self.vars: # var = getattr(context, varname) # if var.locked: # var.unlock() return do_not_raise_exc
async def UploadStorages(self): """ Загрузить хранилища """ for storage in storage_list: with open("Configs/storage_%s.json" % storage.name.lower()) as f: try: d = json.loads(f.read()) queries = list() for entry in d: s = [str(pg_str(v)) for v in entry.values()] queries.append("""INSERT INTO "storage_{0}" ({1}) VALUES ({2}); """.format(storage.name.lower(), ", ".join([f'"{key}"' for key in entry.keys()]), ", ".join([str(pg_str(v)) for v in entry.values()])) ) await self.driver.exec_raw(""" DELETE FROM storage_{0}; {1} """.format(storage.name.lower(), "\n".join(queries))) except json.JSONDecodeError: INFO_MSG("Storage:%s",storage)
def destroy_game(self, game_to_destroy): INFO_MSG(f"{game_to_destroy}") for games in self.games.values(): for game in games: if game.is_subgame(game_to_destroy): game.reset() for player_session in game_to_destroy.players: self.free_player(player_session) return if game_to_destroy in games: games.remove(game_to_destroy) for player_session in game_to_destroy.players: self.free_player(player_session) return WARN_MSG(f"Cannot destroy game {game_to_destroy}, not found")
def dequeue_player(self, session): """ Убрать игрока @param session: сессия игрока @return: """ # session.ue4client.RunConsoleCommand("disconnect") if self.dedicated_server: self.dedicated_server.UnregisterPlayer(session.access_token) super().dequeue_player(session) if not len(self.players): if self.empty_match_timer_handle: self.empty_match_timer_handle.cancel() INFO_MSG(f"Preparing to close and exit game {self}") self.empty_match_timer_handle = self.call_later( self.handle_empty_match, 120.0)
async def TellPID(self, connection, pid: int32): """ Сообщить PID этому сервису @param pid: идентификатор процесса """ if pid in self.processes and not self.processes[pid]['future'].done(): INFO_MSG( f"{connection} ({self.processes[pid]['name']}) tells pid {pid} in {round(time() - self.processes[pid]['opened_at'], 6)} seconds" ) self.processes[pid]['future'].set_result( await self.create_client_connection(self.processes[pid]['endpoint'])) else: ERROR_MSG( "Failed to tell PID! " f"There are no processes runned with pid {pid} or this process already told PID." "Probably you run process which lanuched child process (be sure if this is UE4 dedicated server, " "check the right path, not a PROJECTNAME.exe in root dir)")
async def create_client_connection(self, endpoint, on_lost=None): INFO_MSG(f"{endpoint}") if endpoint not in self.tcp_server.clients: _, connection = await create_connection( endpoint, on_lost=on_lost ) # await TCPClient(endpoint, ClientConnectionHandler, do_open_connection=True) if is_valid(connection): self.tcp_server.clients[endpoint] = connection connection.add_lost_callback( partial(self.on_lost_client, endpoint)) return connection else: self.tcp_server.clients[endpoint].add_lost_callback(on_lost) connection = self.tcp_server.clients[endpoint] connection.add_lost_callback(partial(self.on_lost_client, endpoint)) if not is_valid(connection): del self.tcp_server.clients[endpoint] return connection
async def ConfirmUser(self, digest: FString) -> (Bool, FString): unconfirmeds = await self.driver.exec_raw(""" SELECT * FROM public.users_uncofirmed WHERE digest=%s; """, digest) for unconfirmed_user in unconfirmeds: id, digest = get_rows(unconfirmed_user, 2) users = await self.driver.exec_raw(""" SELECT unique_name FROM public.users WHERE id=%s; """, id) for user in users: await self.driver.exec_raw(""" UPDATE public.users SET confirmed = TRUE WHERE id=%s; DELETE FROM users_uncofirmed WHERE id=%s; """, id, id) username = user[0] INFO_MSG(f"User {username} confirmed") return True, username else: WARN_MSG(f"No user with id={id} and digest={digest}") return False, ""
async def async_wakeup_service_locally(self, service_path, arguments, port, is_python_process=True, index=0, name=None): INFO_MSG( f"{service_path}, {arguments}, {port}, {is_python_process}, {index}, {name}" ) if arguments is None: arguments = dict() access_token = Access().generate(AccessLevel.Internal) # WARN_MSG(f"Opening (causer {self.exposed_ip}") proc = self.open_process(service_path, is_python_process=is_python_process, service_port=port, causer_ip=self.endpoint[0], causer_exposed_ip=self.exposed_ip, causer_port=self.endpoint[1], postfix=('[%i]' % index) if index else "", region=self.config.get_region(), **arguments, access_token=access_token, is_child_process=True) future_data = self.processes[proc.pid] = { 'future': asyncio.Future(loop=asyncio.get_event_loop()), 'endpoint': (self.endpoint[0], port), 'opened_at': time(), 'name': name } try: service_connection = await future_data['future'] except Exception as e: ERROR_MSG("Something went wrong in future", e) return None return service_connection, proc
def open_process(self, service_path, arguments=list(), is_python_process=True, extented_param=None, **kwargs): cmd_kwargs = list() kwargs['no_color_patterns'] = CommandLine.get_arguments( ).no_color_patterns for key, value in kwargs.items(): if CommandLine.has_arg(key): arg_type = CommandLine.get_arg_type(key) kwarg = "-%s=%s" % (key, arg_type(value)) cmd_kwargs.append(kwarg) else: ERROR_MSG( "Unable to pass %s parameter, not exists in CommandLine.py: Arguments class" % key) # cmd_kwargs = ["-%s=%s" % (key, value) for key, value in kwargs.items()] cmd_args = list() python_executable_name = ConfigGlobals.PythonExecutable if is_python_process: cmd_args.append(python_executable_name) cmd_args.extend(service_path) if isinstance( service_path, list) else cmd_args.append(service_path) if extented_param is not None: cmd_args.append(extented_param) cmd_args.extend(cmd_kwargs) cmd_args.extend(arguments) process = subprocess.Popen(cmd_args, shell=False) INFO_MSG(f"Opening process {process.pid}", cmd_args) # print(cmd_args) return process
async def create_prepared(cls, match_making, game_type, extra=None): """ Создаёт новый инстанс матча @param match_making: @param game_type: @return: """ maps_by_type = Maps.get_all_by("Type", game_type) INFO_MSG("Maps: %s" % maps_by_type) if not maps_by_type: raise RuntimeError("Gameplay maps list is empty") selected_map: FMapInfo = random.choice(maps_by_type) if isinstance(extra, dict): if extra.get('force_use_map', None): selected_map = Maps.get_by("Name", extra['force_use_map']) return await cls(match_making, id=match_making.new_id(), map_name=selected_map.Name, max_players=selected_map.PlayersMax)
async def __ainit__(self, context_name, entity_typename, endpoint_or_connection, remote_id, existent_endpoint_and_connection=None): INFO_MSG("") self.remote_id = remote_id self.client_connection: TCPClient if existent_endpoint_and_connection is None: self.client_connection, self.endpoint = await self.get_connection_and_endpoint_with_lost_callback( endpoint_or_connection) else: self.client_connection, self.endpoint = existent_endpoint_and_connection self.initialized_flag = True self.context_name = context_name self.entity_typename = entity_typename self.entity_info = ConfigurationGenerator( ).generated_entities_info.get_by_name(entity_typename) self.lost_callbacks = []
async def __ainit__(self): await super().__ainit__() self.started_time = time() args = self.get_args() self.next_service_port = self.child_service_port_start cls_name = self.__class__.__name__ config: AppConfigType = Configuration()[cls_name] if not config: ERROR_MSG("Failed to read config! There is no section for %s" % cls_name) self.config = config Globals.no_logging = self.config.DisableLog # INFO_MSG('~') if self.config.Kind in [AppKind.Single, AppKind.Static]: self.endpoint = self.config.get_endpoint() self.exposed_ip = self.config.get_exposed_ip() # INFO_MSG("Test") if args.service_port is not None: self.endpoint = self.endpoint[0], args.service_port # ERROR_MSG(f"T {args.causer_exposed_ip}") if not args.causer_exposed_ip or args.causer_exposed_ip == '0.0.0.0': try: self.exposed_ip = self.config.get_exposed_ip() except NotImplementedError: self.exposed_ip = '...' serving = not self.config.NoServer self.tcp_server = await create_server(self.endpoint, serving) Globals.disabled_log_categories = ConfigGlobals.DisabledLogs if not self.get_args().silent: INFO_MSG("%s started! with %s" % (cls_name, self.get_args())) INFO_MSG("%s started at %s (%s)" % (cls_name, self.endpoint if serving else "None", self.get_region())) INFO_MSG("Version: %s, generator signature: %s" % (Globals.version, Globals.generator_signature)) INFO_MSG("Description: %s" % self.config.Description) self.processes = dict() self.postfix = self.get_args().postfix if args.causer_exposed_ip and args.causer_exposed_ip != '0.0.0.0': self.exposed_ip = args.causer_exposed_ip INFO_MSG(f"Causer exposed {self.exposed_ip}") await self.start() self.dedicated_servers = list() if args.causer_ip is not None and args.causer_port is not None: INFO_MSG(f"Call to causer: {args.causer_ip}:{args.causer_port}") mbox = await Service.make_mailbox( "base", "Service", (args.causer_ip, args.causer_port)) await mbox.TellPID(os.getpid())