Exemple #1
0
 def __init__(self, name):
     super().__init__(name)
     self.logger = get_logger(name)
     self.__key = hash("BaseScript:" + name)
     self.game_tick_packet = GameTickPacket()
     self.ball_prediction = BallPrediction()
     self.game_interface = GameInterface(self.logger)
     self.game_interface.load_interface()
     fake_index = random.randint(
         100,
         10000)  # a number unlikely to conflict with bots or other scripts
     self.renderer = self.game_interface.renderer.get_rendering_manager(
         bot_index=fake_index, bot_team=2)
     # Get matchcomms root if provided as a command line argument.
     try:
         pos = sys.argv.index("--matchcomms-url")
         potential_url = urlparse(sys.argv[pos + 1])
     except (ValueError, IndexError):
         # Missing the command line argument.
         pass
     else:
         if potential_url.scheme == "ws" and potential_url.netloc:
             self.matchcomms_root = potential_url
         else:
             raise ValueError("The matchcomms url is invalid")
Exemple #2
0
    def __init__(self, agent_metadata_queue, quit_event, options):
        super().__init__(agent_metadata_queue, quit_event, options)

        # Sets up the logger. The string is the name of your hivemind.
        # This is configured inside your config file under hivemind_name.
        self.logger = get_logger(options['name'])

        # Give a warning if exec_path was given.
        if self.exec_path is not None:
            self.logger.warning(
                "An exec_path was given, but this is a PythonHivemind, and so it is ignored."
            )
            self.logger.warning(
                "  Try subclassing SubprocessHivemind if you want to use an executable."
            )

        # The game interface is how you get access to things
        # like ball prediction, the game tick packet, or rendering.
        self.game_interface = GameInterface(self.logger)

        # drone_indices is a set of bot indices
        # which requested this hivemind with the same key.
        self.drone_indices = set()

        self._field_info = FieldInfoPacket()
Exemple #3
0
def _grade_exercise(game_interface: GameInterface, ex: Exercise, seed: int) -> Result:
    grade = None
    rate_limit = rate_limiter.RateLimiter(120)
    last_tick_game_time = None  # What the tick time of the last observed tick was
    game_tick_packet = GameTickPacket()  # We want to do a deep copy for game inputs so people don't mess with em

    # Run until the Exercise finishes.
    while grade is None:

        # Read from game data shared memory
        game_interface.update_live_data_packet(game_tick_packet)

        # Run ex.on_tick() only if the game_info has updated.
        tick_game_time = game_tick_packet.game_info.seconds_elapsed
        if tick_game_time != last_tick_game_time:
            last_tick_game_time = tick_game_time
            try:
                grade = ex.on_tick(game_tick_packet)
                ex.render(game_interface.renderer)
            except Exception as e:
                return Result(ex, seed, FailDueToExerciseException(e, traceback.format_exc()))

        rate_limit.acquire()

    return Result(ex, seed, grade)
Exemple #4
0
 def __init__(self, terminate_request_event, termination_complete_event, reload_request_event, bot_configuration,
              name, team, index, agent_class_wrapper, agent_metadata_queue, quick_chat_queue_holder):
     """
     :param terminate_request_event: an Event (multiprocessing) which will be set from the outside when the program is trying to terminate
     :param termination_complete_event: an Event (multiprocessing) which should be set from inside this class when termination has completed successfully
     :param reload_request_event: an Event (multiprocessing) which will be set from the outside to force a reload of the agent
     :param reload_complete_event: an Event (multiprocessing) which should be set from inside this class when reloading has completed successfully
     :param bot_configuration: parameters which will be passed to the bot's constructor
     :param name: name which will be passed to the bot's constructor. Will probably be displayed in-game.
     :param team: 0 for blue team or 1 for orange team. Will be passed to the bot's constructor.
     :param index: The player index, i.e. "this is player number <index>". Will be passed to the bot's constructor.
         Can be used to pull the correct data corresponding to the bot's car out of the game tick packet.
     :param agent_class_wrapper: The ExternalClassWrapper object that can be used to load and reload the bot
     :param agent_metadata_queue: a Queue (multiprocessing) which expects to receive certain metadata about the agent once available.
     :param quick_chat_queue_holder: A data structure which helps the bot send and receive quickchat
     """
     self.terminate_request_event = terminate_request_event
     self.termination_complete_event = termination_complete_event
     self.reload_request_event = reload_request_event
     self.bot_configuration = bot_configuration
     self.name = name
     self.team = team
     self.index = index
     self.agent_class_wrapper = agent_class_wrapper
     self.agent_metadata_queue = agent_metadata_queue
     self.logger = get_logger('bot' + str(self.index))
     self.game_interface = GameInterface(self.logger)
     self.quick_chat_queue_holder = quick_chat_queue_holder
     self.last_chat_time = time.time()
     self.chat_counter = 0
     self.reset_chat_time = True
     self.game_tick_packet = None
     self.bot_input = None
Exemple #5
0
 def __init__(self):
     self.logger = get_logger(DEFAULT_LOGGER)
     self.game_interface = GameInterface(self.logger)
     self.quick_chat_manager = QuickChatManager(self.game_interface)
     self.quit_event = mp.Event()
     self.helper_process_manager = HelperProcessManager(self.quit_event)
     self.bot_quit_callbacks = []
     self.agent_metadata_map = {}
Exemple #6
0
 def __init__(self, name, team, index):
     super().__init__(name, team, index)
     self.logger = get_logger('ExeSocket' + str(self.index))
     self.is_retired = False
     self.executable_path = None
     self.game_interface = GameInterface(self.logger)
     self.game_tick_packet = GameTickPacket()
     self.spawn_id_seen = False
 def __init__(self):
     self.logger = get_logger('packet reader')
     self.game_interface = GameInterface(self.logger)
     self.game_interface.inject_dll()
     self.game_interface.load_interface()
     self.game_tick_packet = game_data_struct.GameTickPacket()
     self.rate_limit = rate_limiter.RateLimiter(GAME_TICK_PACKET_REFRESHES_PER_SECOND)
     self.last_call_real_time = datetime.now()  # When we last called the Agent
Exemple #8
0
 def __init__(self, agent_metadata_queue, quit_event, options):
     super().__init__(agent_metadata_queue, quit_event, options)
     self.logger = get_logger('scratch_mgr')
     self.game_interface = GameInterface(self.logger)
     self.current_sockets = set()
     self.running_indices = set()
     self.port: int = options['port']
     self.sb3_file = options['sb3-file']
     self.has_received_input = False
Exemple #9
0
 def __init__(self):
     self.logger = get_logger(DEFAULT_LOGGER)
     self.game_interface = GameInterface(self.logger)
     self.quick_chat_manager = QuickChatManager(self.game_interface)
     self.quit_event = mp.Event()
     self.helper_process_manager = HelperProcessManager(self.quit_event)
     self.bot_quit_callbacks = []
     self.bot_reload_requests = []
     self.agent_metadata_map = {}
     self.ball_prediction_process = None
     self.match_config: MatchConfig = None
Exemple #10
0
 def __init__(self, terminate_request_event, termination_complete_event,
              reload_request_event, bot_configuration, name, team, index,
              agent_class_wrapper, agent_metadata_queue,
              match_config: MatchConfig, matchcomms_root: URL,
              spawn_id: int):
     """
     :param terminate_request_event: an Event (multiprocessing) which will be set from the outside when the program is trying to terminate
     :param termination_complete_event: an Event (multiprocessing) which should be set from inside this class when termination has completed successfully
     :param reload_request_event: an Event (multiprocessing) which will be set from the outside to force a reload of the agent
     :param reload_complete_event: an Event (multiprocessing) which should be set from inside this class when reloading has completed successfully
     :param bot_configuration: parameters which will be passed to the bot's constructor
     :param name: name which will be passed to the bot's constructor. Will probably be displayed in-game.
     :param team: 0 for blue team or 1 for orange team. Will be passed to the bot's constructor.
     :param index: The player index, i.e. "this is player number <index>". Will be passed to the bot's constructor.
         Can be used to pull the correct data corresponding to the bot's car out of the game tick packet.
     :param agent_class_wrapper: The ExternalClassWrapper object that can be used to load and reload the bot
     :param agent_metadata_queue: a Queue (multiprocessing) which expects to receive AgentMetadata once available.
     :param match_config: Describes the match that is being played.
     :param matchcomms_root: The server to connect to if you want to communicate to other participants in the match.
     :param spawn_id: The identifier we expect to see in the game tick packet at our player index. If it does not
         match, then we will force the agent to retire. Pass None to opt out of this behavior.
     """
     self.terminate_request_event = terminate_request_event
     self.termination_complete_event = termination_complete_event
     self.reload_request_event = reload_request_event
     self.bot_configuration = bot_configuration
     self.name = name
     self.team = team
     self.index = index
     self.agent_class_wrapper = agent_class_wrapper
     self.agent_metadata_queue = agent_metadata_queue
     self.logger = get_logger('bot' + str(self.index))
     self.game_interface = GameInterface(self.logger)
     self.last_chat_time = time.time()
     self.chat_counter = 0
     self.reset_chat_time = True
     self.game_tick_packet = None
     self.bot_input = None
     self.ball_prediction = None
     self.rigid_body_tick = None
     self.match_config = match_config
     self.matchcomms_root = matchcomms_root
     self.last_message_index = 0
     self.agent = None
     self.agent_class_file = None
     self.last_module_modification_time = 0
     self.scan_last = 0
     self.scan_temp = 0
     self.file_iterator = None
     self.maximum_tick_rate_preference = bot_configuration.get(
         BOT_CONFIG_MODULE_HEADER, MAXIMUM_TICK_RATE_PREFERENCE_KEY)
     self.spawn_id = spawn_id
     self.spawn_id_seen = False
     self.counter = 0
Exemple #11
0
 def __init__(self):
     self.logger = get_logger(DEFAULT_LOGGER)
     self.game_interface = GameInterface(self.logger)
     self.quit_event = mp.Event()
     self.helper_process_manager = HelperProcessManager(self.quit_event)
     self.bot_quit_callbacks = []
     self.bot_reload_requests = []
     self.agent_metadata_map = {}
     self.match_config: MatchConfig = None
     self.rlbot_gateway_process = None
     self.matchcomms_server: MatchcommsServerThread = None
Exemple #12
0
def _setup_exercise(game_interface: GameInterface, ex: Exercise, seed: int) -> Optional[Result]:
    """
    Set the game state.
    Only returns a result if there was an error in ex.setup()
    """
    rng = random.Random()
    rng.seed(seed)
    try:
        game_state = ex.setup(rng)
    except Exception as e:
        return Result(ex, seed, FailDueToExerciseException(e, traceback.format_exc()))
    game_interface.set_game_state(game_state)
Exemple #13
0
 def __init__(self, name):
     super().__init__(name)
     self.logger = get_logger(name)
     self.__key = hash("BaseScript:" + name)
     self.game_tick_packet = GameTickPacket()
     self.ball_prediction = BallPrediction()
     self.game_interface = GameInterface(self.logger)
     self.game_interface.load_interface()
     fake_index = random.randint(
         100,
         10000)  # a number unlikely to conflict with bots or other scripts
     self.renderer = self.game_interface.renderer.get_rendering_manager(
         bot_index=fake_index, bot_team=2)
Exemple #14
0
 def __init__(self, agent_metadata_queue, quit_event, options):
     super().__init__(agent_metadata_queue, quit_event, options)
     self.logger = get_logger('scratch_mgr')
     self.game_interface = GameInterface(self.logger)
     self.current_sockets = set()
     self.running_indices = set()
     self.metadata_map = dict()
     self.port: int = options['port']
     self.sb3_file = options['sb3-file']
     self.pretend_blue_team = options['pretend_blue_team']
     self.has_received_input = False
     self.scratch_index_to_rlbot = {}
     self.should_flip_field = False
Exemple #15
0
    def __init__(self, agent_metadata_queue, quit_event, options):
        super().__init__(agent_metadata_queue, quit_event, options)

        # Sets up the logger. The string is the name of your hivemind.
        # Call this something unique so people can differentiate between hiveminds.
        self.logger = get_logger('Example Hivemind')

        # The game interface is how you get access to things
        # like ball prediction, the game tick packet, or rendering.
        self.game_interface = GameInterface(self.logger)

        # Running indices is a set of bot indices
        # which requested this hivemind with the same key.
        self.running_indices = set()
Exemple #16
0
 def __init__(self):
     self.logger = get_logger(DEFAULT_LOGGER)
     self.game_interface = GameInterface(self.logger)
     self.quit_event = mp.Event()
     self.helper_process_manager = HelperProcessManager(self.quit_event)
     self.bot_quit_callbacks = []
     self.bot_reload_requests = []
     self.agent_metadata_map: Dict[int, AgentMetadata] = {}
     self.match_config: MatchConfig = None
     self.launcher_preference = None
     self.rlbot_gateway_process = None
     self.matchcomms_server: MatchcommsServerThread = None
     self.early_start_seconds = 0
     self.num_metadata_received = 0
Exemple #17
0
def _wait_until_good_ticks(game_interface: GameInterface, required_new_ticks: int=3):
    """Blocks until we're getting new packets, indicating that the match is ready."""
    rate_limit = rate_limiter.RateLimiter(120)
    last_tick_game_time = None  # What the tick time of the last observed tick was
    game_tick_packet = GameTickPacket()  # We want to do a deep copy for game inputs so people don't mess with em
    seen_times = 0
    while seen_times < required_new_ticks:

        # Read from game data shared memory
        game_interface.update_live_data_packet(game_tick_packet)
        tick_game_time = game_tick_packet.game_info.seconds_elapsed
        if tick_game_time != last_tick_game_time and game_tick_packet.game_info.is_round_active:
            last_tick_game_time = tick_game_time
            seen_times += 1

        rate_limit.acquire()
    def __init__(self, queue, choreo_obj):
        # Sets up the logger. The string is the name of your hivemind.
        # Call this something unique so people can differentiate between hiveminds.
        self.logger = get_logger('Choreography Hivemind')

        # The game interface is how you get access to things
        # like ball prediction, the game tick packet, or rendering.
        self.game_interface = GameInterface(self.logger)

        self.drones = []

        self.choreo = choreo_obj(self.game_interface)
        self.choreo.generate_sequence(self.drones)

        # Set up queue to know when to stop and reload.
        self.queue = queue
Exemple #19
0
def _wait_until_good_ticks(game_interface: GameInterface, required_new_ticks: int=3):
    """Blocks until we're getting new packets, indicating that the match is ready."""
    rate_limit = rate_limiter.RateLimiter(120)
    last_tick_game_time = None  # What the tick time of the last observed tick was
    packet = GameTickPacket()  # We want to do a deep copy for game inputs so people don't mess with em
    seen_times = 0
    while seen_times < required_new_ticks:
        game_interface.update_live_data_packet(packet)
        def is_good_tick():
            if packet.game_info.seconds_elapsed == last_tick_game_time: return False
            if not packet.game_info.is_round_active: return False
            if any(car.is_demolished for car in packet.game_cars): return False
            return True
        if is_good_tick():
            seen_times += 1
        last_tick_game_time = packet.game_info.seconds_elapsed

        rate_limit.acquire()
Exemple #20
0
 def __init__(self):
     self.game_interface = GameInterface(get_logger("Commentator"))
     self.game_interface.load_interface()
     self.game_interface.wait_until_loaded()
     self.touchTimer = 0
     self.currentTime = 0
     self.firstIter = True
     self.overTime = False
     self.shotDetection = True
     self.ballHistory = []
     self.lastTouches = []
     self.teams = []
     self.joinTimer = 0
     self.q = Queue(maxsize=3)
     self.host = threading.Thread(target=host, args=(self.q,))
     self.host.start()
     self.main()
     self.host.join()
Exemple #21
0
    def __init__(self, agent_metadata_queue, quit_event, options):
        super().__init__(agent_metadata_queue, quit_event, options)

        # Controls.
        pygame.init()
        pygame.joystick.init()
        self.controller = pygame.joystick.Joystick(0)
        self.controller.init()
        self.axis_data = [0] * self.controller.get_numaxes()
        self.button_data = [False] * self.controller.get_numbuttons()

        # RLBot.
        self.index = options["index"]
        self.game_interface = GameInterface(get_logger(str(self.index)))

        self.recording = False
        self.recorded = []
        self.recording_time = None
Exemple #22
0
def arrange_in_ground_circle(drones: List[Drone],
                             game_interface: GameInterface, radius: float,
                             radian_offset: float):
    if len(drones) == 0:
        return

    car_states = {}
    radian_spacing = 2 * math.pi / len(drones)

    for index, drone in enumerate(drones):
        progress = index * radian_spacing + radian_offset
        target = Vec3(radius * math.sin(progress), radius * math.cos(progress),
                      1000)

        car_states[drone.index] = CarState(
            Physics(location=Vector3(target.x, target.y, 50),
                    velocity=Vector3(0, 0, 0),
                    rotation=Rotator(0, -progress, 0)))
    game_interface.set_game_state(GameState(cars=car_states))
Exemple #23
0
 def __init__(self, terminate_request_event, termination_complete_event,
              reload_request_event, bot_configuration, name, team, index,
              agent_class_wrapper, agent_metadata_queue,
              match_config: MatchConfig, matchcomms_root: URL):
     """
     :param terminate_request_event: an Event (multiprocessing) which will be set from the outside when the program is trying to terminate
     :param termination_complete_event: an Event (multiprocessing) which should be set from inside this class when termination has completed successfully
     :param reload_request_event: an Event (multiprocessing) which will be set from the outside to force a reload of the agent
     :param reload_complete_event: an Event (multiprocessing) which should be set from inside this class when reloading has completed successfully
     :param bot_configuration: parameters which will be passed to the bot's constructor
     :param name: name which will be passed to the bot's constructor. Will probably be displayed in-game.
     :param team: 0 for blue team or 1 for orange team. Will be passed to the bot's constructor.
     :param index: The player index, i.e. "this is player number <index>". Will be passed to the bot's constructor.
         Can be used to pull the correct data corresponding to the bot's car out of the game tick packet.
     :param agent_class_wrapper: The ExternalClassWrapper object that can be used to load and reload the bot
     :param agent_metadata_queue: a Queue (multiprocessing) which expects to receive AgentMetadata once available.
     :param match_config: Describes the match that is being played.
     :param matchcomms_root: The server to connect to if you want to communicate to other participants in the match.
     """
     self.terminate_request_event = terminate_request_event
     self.termination_complete_event = termination_complete_event
     self.reload_request_event = reload_request_event
     self.bot_configuration = bot_configuration
     self.name = name
     self.team = team
     self.index = index
     self.agent_class_wrapper = agent_class_wrapper
     self.agent_metadata_queue = agent_metadata_queue
     self.logger = get_logger('bot' + str(self.index))
     self.game_interface = GameInterface(self.logger)
     self.last_chat_time = time.time()
     self.chat_counter = 0
     self.reset_chat_time = True
     self.game_tick_packet = None
     self.bot_input = None
     self.ball_prediction = None
     self.rigid_body_tick = None
     self.match_config = match_config
     self.matchcomms_root = matchcomms_root
     self.last_message_index = 0
     self.agent = None
     self.agent_class_file = None
     self.last_module_modification_time = 0
Exemple #24
0
class GameTickReader:
    def __init__(self):
        self.logger = get_logger('packet reader')
        self.game_interface = GameInterface(self.logger)
        self.game_interface.load_interface()
        self.game_tick_packet = game_data_struct.GameTickPacket()

        #self.rate_limit = rate_limiter.RateLimiter(GAME_TICK_PACKET_REFRESHES_PER_SECOND)
        self.last_call_real_time = datetime.now()  # When we last called the Agent

    def get_packet(self):
        now = datetime.now()
        #self.rate_limit.acquire()
        self.last_call_real_time = now

        self.pull_data_from_game()
        return self.game_tick_packet

    def pull_data_from_game(self):
        self.game_interface.update_live_data_packet(self.game_tick_packet)
Exemple #25
0
def _grade_exercise(game_interface: GameInterface, ex: Exercise,
                    seed: int) -> Result:
    grade = None
    game_tick_packet = GameTickPacket(
    )  # We want to do a deep copy for game inputs so people don't mess with em

    # Run until the Exercise finishes.
    while grade is None:

        # Read from game data shared memory
        game_interface.fresh_live_data_packet(game_tick_packet, 100, 99)

        try:
            grade = ex.on_tick(game_tick_packet)
            ex.render(game_interface.renderer)
        except Exception as e:
            return Result(
                ex, seed, FailDueToExerciseException(e,
                                                     traceback.format_exc()))

    return Result(ex, seed, grade)
Exemple #26
0
class GameTickReader:
    def __init__(self):
        self.logger = get_logger('packet reader')
        self.game_interface = GameInterface(self.logger)
        self.game_interface.inject_dll()
        self.game_interface.load_interface()
        self.game_tick_packet = game_data_struct.GameTickPacket()

    def get_packet(self):
        self.game_interface.update_live_data_packet(self.game_tick_packet)
        return self.game_tick_packet
Exemple #27
0
class SetupManager:
    """
    This class is responsible for pulling together all bits of the framework to
    set up a match between agents.

    A normal order of methods would be:
        connect_to_game()
        load_config()
        launch_ball_prediction()
        launch_bot_processes()
        start_match()
        infinite_loop()
        # the below two might be from another thread
        reload_all_agents()
        shut_down()
    """
    has_started = False
    num_participants = None
    names = None
    teams = None
    python_files = None
    bot_bundles: List[BotConfigBundle] = None
    start_match_configuration = None
    agent_metadata_queue = None
    extension = None
    bot_processes: Dict[int, mp.Process] = {}

    def __init__(self):
        self.logger = get_logger(DEFAULT_LOGGER)
        self.game_interface = GameInterface(self.logger)
        self.quit_event = mp.Event()
        self.helper_process_manager = HelperProcessManager(self.quit_event)
        self.bot_quit_callbacks = []
        self.bot_reload_requests = []
        self.agent_metadata_map = {}
        self.match_config: MatchConfig = None
        self.rlbot_gateway_process = None
        self.matchcomms_server: MatchcommsServerThread = None
        self.early_start_seconds = 0
        self.num_metadata_received = 0

    def is_rocket_league_running(self, port) -> bool:
        """
        Returns whether Rocket League is running with the right port.
        """

        try:
            is_rocket_league_running, proc = process_configuration.is_process_running(
                ROCKET_LEAGUE_PROCESS_INFO.PROGRAM,
                ROCKET_LEAGUE_PROCESS_INFO.PROGRAM_NAME,
                ROCKET_LEAGUE_PROCESS_INFO.REQUIRED_ARGS)

            if proc is not None:
                # Check for correct port.
                rocket_league_port = self._read_port_from_rocket_league_args(
                    proc.cmdline())
                if rocket_league_port is not None and rocket_league_port != port:
                    raise Exception(
                        f"Rocket League is already running with port {rocket_league_port} but we wanted "
                        f"{port}! Please close Rocket League and let us start it for you instead!"
                    )
        except WrongProcessArgs:
            raise Exception(
                f"Rocket League is not running with {ROCKET_LEAGUE_PROCESS_INFO.REQUIRED_ARGS}!\n"
                "Please close Rocket League and let us start it for you instead!"
            )

        return is_rocket_league_running

    def connect_to_game(self):
        """
        Connects to the game by initializing self.game_interface.
        """
        version.print_current_release_notes()
        port = self.ensure_rlbot_gateway_started()

        # Prevent loading game interface twice.
        if self.has_started:
            if not self.is_rocket_league_running(port):
                raise Exception(
                    "Rocket League is not running even though we started it once.\n"
                    "Please restart RLBot.")
            return

        # Currently match_config is None when launching from RLBotGUI.
        if self.match_config is not None and self.match_config.networking_role == 'remote_rlbot_client':
            self.logger.info(
                "Will not start Rocket League because this is configured as a client!"
            )
        # Launch the game if it is not running.
        elif not self.is_rocket_league_running(port):
            self.launch_rocket_league(port=port)

        try:
            self.game_interface.load_interface()
        except Exception as e:
            self.logger.error("Terminating rlbot gateway and raising:")
            self.rlbot_gateway_process.terminate()
            raise e
        self.agent_metadata_queue = mp.Queue()
        self.has_started = True

    @staticmethod
    def _read_port_from_rocket_league_args(args):
        for arg in args:
            # The arg will look like RLBot_ControllerURL="127.0.0.1:23233"
            if 'RLBot_ControllerURL' in arg:
                rocket_league_port = int(arg.split(':')[1].replace('"', ''))
                return int(rocket_league_port)
        return None

    def launch_rocket_league(self, port):
        """
        Launches Rocket League but does not connect to it.
        """
        ideal_args = ROCKET_LEAGUE_PROCESS_INFO.get_ideal_args(port)
        self.logger.info(f'Launching Rocket League with args: {ideal_args}')

        # Try launch via Steam.
        steam_exe_path = try_get_steam_executable_path()
        if steam_exe_path:  # Note: This Python 3.8 feature would be useful here https://www.python.org/dev/peps/pep-0572/#abstract
            exe_and_args = [
                str(steam_exe_path),
                '-applaunch',
                str(ROCKET_LEAGUE_PROCESS_INFO.GAMEID),
            ] + ideal_args
            _ = subprocess.Popen(
                exe_and_args)  # This is deliberately an orphan process.
            return

        # TODO: Figure out launching via Epic games

        self.logger.warning('Using fall-back launch method.')
        self.logger.info(
            "You should see a confirmation pop-up, if you don't see it then click on Steam! "
            'https://gfycat.com/AngryQuickFinnishspitz')
        args_string = '%20'.join(ideal_args)

        # Try launch via terminal (Linux)
        if platform.system() == 'Linux':
            linux_args = [
                'steam',
                f'steam://rungameid/{ROCKET_LEAGUE_PROCESS_INFO.GAMEID}//{args_string}'
            ]

            try:
                _ = subprocess.Popen(linux_args)

            except OSError:
                self.logger.warning(
                    'Could not find Steam executable, using browser to open instead.'
                )
            else:
                return

        try:
            webbrowser.open(
                f'steam://rungameid/{ROCKET_LEAGUE_PROCESS_INFO.GAMEID}//{args_string}'
            )
        except webbrowser.Error:
            self.logger.warning(
                'Unable to launch Rocket League. Please launch Rocket League manually using the -rlbot option to continue.'
            )

    def load_match_config(self,
                          match_config: MatchConfig,
                          bot_config_overrides={}):
        """
        Loads the match config into internal data structures, which prepares us to later
        launch bot processes and start the match.

        This is an alternative to the load_config method; they accomplish the same thing.
        """
        self.num_participants = match_config.num_players
        self.names = [bot.name for bot in match_config.player_configs]
        self.teams = [bot.team for bot in match_config.player_configs]

        for player in match_config.player_configs:
            if player.bot and not player.rlbot_controlled:
                set_random_psyonix_bot_preset(player)

        bundles = [
            bot_config_overrides[index] if index in bot_config_overrides else
            get_bot_config_bundle(bot.config_path) if bot.config_path else None
            for index, bot in enumerate(match_config.player_configs)
        ]

        self.python_files = [
            bundle.python_file if bundle else None for bundle in bundles
        ]

        self.bot_bundles = []

        for index, bot in enumerate(match_config.player_configs):
            self.bot_bundles.append(bundles[index])
            if bot.loadout_config is None and bundles[index]:
                looks_config = bundles[index].get_looks_config()
                bot.loadout_config = load_bot_appearance(
                    looks_config, bot.team)

        if match_config.extension_config is not None and match_config.extension_config.python_file_path is not None:
            self.load_extension(match_config.extension_config.python_file_path)

        self.match_config = match_config
        self.start_match_configuration = match_config.create_match_settings()
        self.game_interface.start_match_configuration = self.start_match_configuration

    def load_config(self,
                    framework_config: ConfigObject = None,
                    config_location=DEFAULT_RLBOT_CONFIG_LOCATION,
                    bot_configs=None,
                    looks_configs=None):
        """
        Loads the configuration into internal data structures, which prepares us to later
        launch bot processes and start the match.

        :param framework_config: A config object that indicates what bots to run. May come from parsing a rlbot.cfg.
        :param config_location: The location of the rlbot.cfg file, which will be used to resolve relative paths.
        :param bot_configs: Overrides for bot configurations.
        :param looks_configs: Overrides for looks configurations.
        """
        self.logger.debug('reading the configs')

        # Set up RLBot.cfg
        if framework_config is None:
            framework_config = create_bot_config_layout()
            framework_config.parse_file(config_location, max_index=MAX_PLAYERS)
        if bot_configs is None:
            bot_configs = {}
        if looks_configs is None:
            looks_configs = {}

        match_config = parse_match_config(framework_config, config_location,
                                          bot_configs, looks_configs)
        self.load_match_config(match_config, bot_configs)

    def ensure_rlbot_gateway_started(self) -> int:
        """
        Ensures that RLBot.exe is running. Returns the port that it will be listening on for connections from
        Rocket League. Rocket League should be passed a command line argument so that it starts with this same port.
        :return:
        """

        # TODO: Uncomment this when done with local testing of Remote RLBot.
        self.rlbot_gateway_process, port = gateway_util.find_existing_process()
        if self.rlbot_gateway_process is not None:
            self.logger.info(f"Already have RLBot.exe running! Port is {port}")
            return port

        launch_options = LaunchOptions()
        if self.match_config is not None:  # Currently this is None when launching from RLBotGUI.
            networking_role = NetworkingRole[self.match_config.networking_role]
            launch_options = LaunchOptions(
                networking_role=networking_role,
                remote_address=self.match_config.network_address)

        self.rlbot_gateway_process, port = gateway_util.launch(launch_options)
        self.logger.info(
            f"Python started RLBot.exe with process id {self.rlbot_gateway_process.pid} "
            f"and port {port}")
        return port

    def launch_ball_prediction(self):
        # This does nothing now. It's kept here temporarily so that RLBotGUI doesn't break.
        pass

    def has_received_metadata_from_all_bots(self):
        expected_metadata_calls = sum(
            1 for player in self.match_config.player_configs
            if player.rlbot_controlled)
        return self.num_metadata_received >= expected_metadata_calls

    def launch_early_start_bot_processes(self):
        """
        Some bots can start up before the game is ready and not be bothered by missing
        or strange looking values in the game tick packet, etc. Such bots can opt in to the
        early start category and enjoy extra time to load up before the match starts.
        """

        if self.match_config.networking_role == NetworkingRole.remote_rlbot_client:
            return  # The bot indices are liable to change, so don't start anything yet.

        self.logger.debug("Launching early-start bot processes")
        num_started = self.launch_bot_process_helper(early_starters_only=True)
        self.try_recieve_agent_metadata()
        if num_started > 0 and self.early_start_seconds > 0:
            self.logger.info(
                f"Waiting for {self.early_start_seconds} seconds to let early-start bots load."
            )
            end_time = datetime.now() + timedelta(
                seconds=self.early_start_seconds)
            while datetime.now() < end_time:
                self.try_recieve_agent_metadata()
                time.sleep(0.1)

    def launch_bot_processes(self):
        self.logger.debug("Launching bot processes")
        self.launch_bot_process_helper(early_starters_only=False)

    def launch_bot_process_helper(self, early_starters_only=False):
        # Start matchcomms here as it's only required for the bots.
        self.kill_matchcomms_server()
        self.matchcomms_server = launch_matchcomms_server()

        num_started = 0

        # Launch processes
        # TODO: this might be the right moment to fix the player indices based on a game tick packet.
        packet = game_data_struct.GameTickPacket()
        self.game_interface.update_live_data_packet(packet)

        # TODO: root through the packet and find discrepancies in the player index mapping.
        for i in range(self.num_participants):
            if not self.start_match_configuration.player_configuration[
                    i].rlbot_controlled:
                continue
            if early_starters_only and not self.bot_bundles[
                    i].supports_early_start:
                continue

            bot_manager_spawn_id = None

            if early_starters_only:
                # Don't use a spawn id stuff for the early start system. The bots will be starting up before
                # the car spawns, and we don't want the bot manager to panic.
                participant_index = i
            else:
                participant_index = None
                spawn_id = self.game_interface.start_match_configuration.player_configuration[
                    i].spawn_id
                self.logger.info(
                    f'Player in slot {i} was sent with spawn id {spawn_id}, will search in the packet.'
                )
                for n in range(0, packet.num_cars):
                    packet_spawn_id = packet.game_cars[n].spawn_id
                    self.logger.info(
                        f'Packet index {n} has spawn id {packet_spawn_id}')
                    if spawn_id == packet_spawn_id:
                        self.logger.info(
                            f'Looks good, considering participant index to be {n}'
                        )
                        participant_index = n
                        bot_manager_spawn_id = spawn_id
                if participant_index is None:
                    raise Exception("Unable to determine the bot index!")

            if participant_index not in self.bot_processes:
                reload_request = mp.Event()
                quit_callback = mp.Event()
                self.bot_reload_requests.append(reload_request)
                self.bot_quit_callbacks.append(quit_callback)
                process = mp.Process(
                    target=SetupManager.run_agent,
                    args=(self.quit_event, quit_callback, reload_request,
                          self.bot_bundles[i],
                          str(self.start_match_configuration.
                              player_configuration[i].name), self.teams[i],
                          participant_index, self.python_files[i],
                          self.agent_metadata_queue, self.match_config,
                          self.matchcomms_server.root_url,
                          bot_manager_spawn_id))
                process.start()
                self.bot_processes[i] = process
                num_started += 1

        self.logger.debug(f"Successfully started {num_started} bot processes")
        return num_started

    def launch_quick_chat_manager(self):
        # Quick chat manager is gone since we're using RLBot.exe now.
        # Keeping this function around for backwards compatibility.
        pass

    def start_match(self):

        if self.match_config.networking_role == NetworkingRole.remote_rlbot_client:
            match_settings = self.game_interface.get_match_settings()
            # TODO: merge the match settings into self.match_config
            # And then make sure we still only start the appropriate bot processes
            # that we originally asked for.

        self.logger.info("Python attempting to start match.")
        self.game_interface.start_match()
        time.sleep(
            2
        )  # Wait a moment. If we look too soon, we might see a valid packet from previous game.
        self.game_interface.wait_until_valid_packet()
        self.logger.info("Match has started")

    def infinite_loop(self):
        instructions = "Press 'r' to reload all agents, or 'q' to exit"
        self.logger.info(instructions)
        while not self.quit_event.is_set():
            # Handle commands
            # TODO windows only library
            if platform.system() == 'Windows':
                if msvcrt.kbhit():
                    command = msvcrt.getwch()
                    if command.lower() == 'r':  # r: reload
                        self.reload_all_agents()
                    elif command.lower(
                    ) == 'q' or command == '\u001b':  # q or ESC: quit
                        self.shut_down()
                        break
                    # Print instructions again if a alphabet character was pressed but no command was found
                    elif command.isalpha():
                        self.logger.info(instructions)

            self.try_recieve_agent_metadata()

    def try_recieve_agent_metadata(self):
        """
        Checks whether any of the started bots have posted their AgentMetadata
        yet. If so, we put them on the agent_metadata_map such that we can
        kill their process later when we shut_down(kill_agent_process_ids=True)

        Returns how from how many bots we received metadata from.
        """
        num_recieved = 0
        while True:  # will exit on queue.Empty
            try:
                single_agent_metadata = self.agent_metadata_queue.get(
                    timeout=0.1)
                num_recieved += 1
                self.helper_process_manager.start_or_update_helper_process(
                    single_agent_metadata)
                self.agent_metadata_map[
                    single_agent_metadata.index] = single_agent_metadata
                process_configuration.configure_processes(
                    self.agent_metadata_map, self.logger)
            except queue.Empty:
                self.num_metadata_received += num_recieved
                return num_recieved

    def reload_all_agents(self, quiet=False):
        if not quiet:
            self.logger.info("Reloading all agents...")
        for rr in self.bot_reload_requests:
            rr.set()

    def shut_down(self, time_limit=5, kill_all_pids=False, quiet=False):
        if not quiet:
            self.logger.info("Shutting Down")

        self.quit_event.set()
        end_time = datetime.now() + timedelta(seconds=time_limit)

        # Don't kill RLBot.exe. It needs to keep running because if we're in a GUI
        # that will persist after this shut down, the interface dll in charge of starting
        # matches is already locked in to its shared memory files, and if we start a new
        # RLBot.exe, those files will go stale. https://github.com/skyborgff/RLBot/issues/9

        # Wait for all processes to terminate before terminating main process
        terminated = False
        while not terminated:
            terminated = True
            for callback in self.bot_quit_callbacks:
                if not callback.is_set():
                    terminated = False
            time.sleep(0.1)
            if datetime.now() > end_time:
                self.logger.info("Taking too long to quit, trying harder...")
                break

        self.kill_bot_processes()

        if kill_all_pids:
            self.kill_agent_process_ids()

        self.kill_matchcomms_server()

        # The quit event can only be set once. Let's reset to our initial state
        self.quit_event = mp.Event()
        self.helper_process_manager = HelperProcessManager(self.quit_event)

        if not quiet:
            self.logger.info("Shut down complete!")

    def load_extension(self, extension_filename):
        try:
            extension_class = import_class_with_base(
                extension_filename, BaseExtension).get_loaded_class()
            self.extension = extension_class(self)
            self.game_interface.set_extension(self.extension)
        except FileNotFoundError as e:
            print(f'Failed to load extension: {e}')

    @staticmethod
    def run_agent(terminate_event, callback_event, reload_request,
                  bundle: BotConfigBundle, name, team, index, python_file,
                  agent_telemetry_queue, match_config: MatchConfig,
                  matchcomms_root: URL, spawn_id: str):

        agent_class_wrapper = import_agent(python_file)
        config_file = agent_class_wrapper.get_loaded_class(
        ).base_create_agent_configurations()
        config_file.parse_file(bundle.config_obj,
                               config_directory=bundle.config_directory)

        if hasattr(agent_class_wrapper.get_loaded_class(),
                   "run_independently"):
            bm = BotManagerIndependent(terminate_event, callback_event,
                                       reload_request, config_file, name, team,
                                       index, agent_class_wrapper,
                                       agent_telemetry_queue, match_config,
                                       matchcomms_root, spawn_id)
        elif hasattr(agent_class_wrapper.get_loaded_class(),
                     "get_output_flatbuffer"):
            bm = BotManagerFlatbuffer(terminate_event, callback_event,
                                      reload_request, config_file, name, team,
                                      index, agent_class_wrapper,
                                      agent_telemetry_queue, match_config,
                                      matchcomms_root, spawn_id)
        else:
            bm = BotManagerStruct(terminate_event, callback_event,
                                  reload_request, config_file, name, team,
                                  index, agent_class_wrapper,
                                  agent_telemetry_queue, match_config,
                                  matchcomms_root, spawn_id)
        bm.run()

    def kill_bot_processes(self):
        for process in self.bot_processes.values():
            process.terminate()
        for process in self.bot_processes.values():
            process.join(timeout=1)
        self.bot_processes.clear()
        self.num_metadata_received = 0

    def kill_agent_process_ids(self):
        pids = process_configuration.extract_all_pids(self.agent_metadata_map)
        for pid in pids:
            try:
                parent = psutil.Process(pid)
                for child in parent.children(
                        recursive=True
                ):  # or parent.children() for recursive=False
                    self.logger.info(f"Killing {child.pid} (child of {pid})")
                    try:
                        child.kill()
                    except psutil._exceptions.NoSuchProcess:
                        self.logger.info("Already dead.")
                self.logger.info(f"Killing {pid}")
                try:
                    parent.kill()
                except psutil._exceptions.NoSuchProcess:
                    self.logger.info("Already dead.")
            except psutil.NoSuchProcess:
                self.logger.info("Can't fetch parent process, already dead.")
            except psutil.AccessDenied as ex:
                self.logger.error(
                    f"Access denied when trying to kill a bot pid! {ex}")
            except Exception as ex:
                self.logger.error(
                    f"Unexpected exception when trying to kill a bot pid! {ex}"
                )

    def kill_matchcomms_server(self):
        if self.matchcomms_server:
            self.matchcomms_server.close()
            self.matchcomms_server = None
Exemple #28
0
class PythonHivemind(BotHelperProcess):

    # Terminology:
    # hivemind - main process controlling the drones.
    # drone - a bot under the hivemind's control.

    # Path to the executable. NOT USED BY PYTHON HIVEMINDS!
    exec_path = None

    def __init__(self, agent_metadata_queue, quit_event, options):
        super().__init__(agent_metadata_queue, quit_event, options)

        # Sets up the logger. The string is the name of your hivemind.
        # This is configured inside your config file under hivemind_name.
        self.logger = get_logger(options['name'])

        # Give a warning if exec_path was given.
        if self.exec_path is not None:
            self.logger.warning(
                "An exec_path was given, but this is a PythonHivemind, and so it is ignored."
            )
            self.logger.warning(
                "  Try subclassing SubprocessHivemind if you want to use an executable."
            )

        # The game interface is how you get access to things
        # like ball prediction, the game tick packet, or rendering.
        self.game_interface = GameInterface(self.logger)

        # drone_indices is a set of bot indices
        # which requested this hivemind with the same key.
        self.drone_indices = set()

    def try_receive_agent_metadata(self):
        """Adds all drones with the correct key to our set of running indices."""
        while not self.metadata_queue.empty():
            # Tries to get the next agent from the queue.
            single_agent_metadata: AgentMetadata = self.metadata_queue.get(
                timeout=1.0)
            # Adds the index to the drone_indices.
            self.drone_indices.add(single_agent_metadata.index)

    def start(self):
        """Starts the BotHelperProcess - Hivemind."""
        # Prints an activation message into the console.
        # This lets you know that the process is up and running.
        self.logger.debug("Hello, world!")

        # Loads game interface.
        self.game_interface.load_interface()

        # Collect drone indices that requested a helper process with our key.
        self.logger.info("Collecting drones; give me a moment.")
        self.try_receive_agent_metadata()
        self.logger.info("Ready to go!")

        # Runs the game loop where the hivemind will spend the rest of its time.
        self.__game_loop()

    def __game_loop(self):
        """
        The bot hivemind will stay in this loop for the whole game. 
        This is where the initialize_hive and get_outputs functions are called.
        """

        # Creating ball prediction and field info objects to later update in wrapper methods.
        self._ball_prediction = BallPrediction()
        self._field_info = FieldInfoPacket()
        self.game_interface.update_field_info_packet(self._field_info)
        # Wrapper for renderer.
        self.renderer: RenderingManager = self.game_interface.renderer

        # Create packet object.
        packet = GameTickPacket()
        # Uses one of the drone indices as a key.
        key = next(iter(self.drone_indices))
        self.game_interface.fresh_live_data_packet(packet, 20, key)

        # Initialization step for your hivemind.
        self.initialize_hive(packet)

        while not self.quit_event.is_set():
            try:
                # Updating the packet.
                self.game_interface.fresh_live_data_packet(packet, 20, key)

                # Get outputs from hivemind for each bot.
                # Outputs are expected to be a Dict[int, PlayerInput]
                outputs = self.get_outputs(packet)

                if outputs is None:
                    self.logger.error("No outputs were returned.")
                    self.logger.error(
                        "  Try putting `return {i: PlayerInput() for i in self.drone_indices}`"
                    )
                    self.logger.error("  in `get_outputs()` to troubleshoot.")
                    continue

                if len(outputs) < len(self.drone_indices):
                    self.logger.error("Not enough outputs were given.")

                elif len(outputs) > len(self.drone_indices):
                    self.logger.error("Too many outputs were given.")

                # Send the drone inputs to the drones.
                for index in outputs:
                    if index not in self.drone_indices:
                        self.logger.error(
                            "Tried to send output to bot index not in drone_indices."
                        )
                        continue
                    output = outputs[index]
                    self.game_interface.update_player_input(output, index)

            except Exception:
                traceback.print_exc()

    # Override these methods:

    def initialize_hive(self, packet: GameTickPacket) -> None:
        """
        Override this method if you want to have an initialization step for your hivemind.
        """
        pass

    def get_outputs(self, packet: GameTickPacket) -> Dict[int, PlayerInput]:
        """Where all the logic of your hivemind gets its input and returns its outputs for each drone.

        Use self.drone_indices to access the set of bot indices your hivemind controls.

        Arguments:
            packet {GameTickPacket} -- see https://github.com/RLBot/RLBot/wiki/Input-and-Output-Data-(current)

        Returns:
            Dict[int, PlayerInput] -- A dictionary with drone indices as keys and corresponding PlayerInputs as values.
        """
        return {index: PlayerInput() for index in self.drone_indices}

    # Wrapper methods to make them the same as if you were making a normal python bot:

    def get_ball_prediction_struct(self) -> BallPrediction:
        self.game_interface.update_ball_prediction(self._ball_prediction)
        return self._ball_prediction

    def get_field_info(self) -> FieldInfoPacket:
        # Field info does not need to be updated.
        return self._field_info

    def get_match_settings(self) -> MatchSettings:
        return self.game_interface.get_match_settings()

    def set_game_state(self, game_state: GameState) -> None:
        self.game_interface.set_game_state(game_state)
Exemple #29
0
class SetupManager:
    """
    This class is responsible for pulling together all bits of the framework to
    set up a match between agents.

    A normal order of methods would be:
        connect_to_game()
        load_config()
        launch_ball_prediction()
        launch_quick_chat_manager()
        launch_bot_processes()
        start_match()
        infinite_loop()
        # the below two might be from another thread
        reload_all_agents()
        shut_down()
    """
    has_started = False
    num_participants = None
    names = None
    teams = None
    python_files = None
    parameters = None
    start_match_configuration = None
    agent_metadata_queue = None
    extension = None
    sub_processes = []

    def __init__(self):
        self.logger = get_logger(DEFAULT_LOGGER)
        self.game_interface = GameInterface(self.logger)
        self.quick_chat_manager = QuickChatManager(self.game_interface)
        self.quit_event = mp.Event()
        self.helper_process_manager = HelperProcessManager(self.quit_event)
        self.bot_quit_callbacks = []
        self.bot_reload_requests = []
        self.agent_metadata_map = {}
        self.ball_prediction_process = None
        self.match_config: MatchConfig = None

    def connect_to_game(self):
        if self.has_started:
            return
        version.print_current_release_notes()
        if not process_configuration.is_process_running(
                ROCKET_LEAGUE_PROCESS_INFO['program'],
                ROCKET_LEAGUE_PROCESS_INFO['program_name']):
            try:
                self.logger.info("Launching Rocket League...")

                webbrowser.open(
                    f"steam://rungameid/{ROCKET_LEAGUE_PROCESS_INFO['gameid']}"
                )
            except webbrowser.Error:
                self.logger.info(
                    "Unable to launch Rocket League automatically. Please launch Rocket League manually to continue."
                )
        self.game_interface.inject_dll()
        self.game_interface.load_interface()
        self.agent_metadata_queue = mp.Queue()
        self.has_started = True

    def load_match_config(self,
                          match_config: MatchConfig,
                          bot_config_overrides={}):
        """
        Loads the match config into internal data structures, which prepares us to later
        launch bot processes and start the match.

        This is an alternative to the load_config method; they accomplish the same thing.
        """
        self.num_participants = match_config.num_players
        self.names = [bot.name for bot in match_config.player_configs]
        self.teams = [bot.team for bot in match_config.player_configs]

        bundles = [
            bot_config_overrides[index] if index in bot_config_overrides else
            get_bot_config_bundle(bot.config_path) if bot.config_path else None
            for index, bot in enumerate(match_config.player_configs)
        ]

        self.python_files = [
            bundle.python_file if bundle else None for bundle in bundles
        ]

        self.parameters = []

        for index, bot in enumerate(match_config.player_configs):
            python_config = None
            if bot.rlbot_controlled:
                python_config = load_bot_parameters(bundles[index])
            self.parameters.append(python_config)
            if bot.loadout_config is None and bundles[index]:
                looks_config = bundles[index].get_looks_config()
                bot.loadout_config = load_bot_appearance(
                    looks_config, bot.team)

        if match_config.extension_config is not None and match_config.extension_config.python_file_path is not None:
            self.load_extension(match_config.extension_config.python_file_path)

        self.match_config = match_config
        self.start_match_configuration = match_config.create_match_settings()
        self.game_interface.start_match_configuration = self.start_match_configuration

    def load_config(self,
                    framework_config: ConfigObject = None,
                    config_location=DEFAULT_RLBOT_CONFIG_LOCATION,
                    bot_configs=None,
                    looks_configs=None):
        """
        Loads the configuration into internal data structures, which prepares us to later
        launch bot processes and start the match.

        :param framework_config: A config object that indicates what bots to run. May come from parsing a rlbot.cfg.
        :param config_location: The location of the rlbot.cfg file, which will be used to resolve relative paths.
        :param bot_configs: Overrides for bot configurations.
        :param looks_configs: Overrides for looks configurations.
        """
        self.logger.debug('reading the configs')

        # Set up RLBot.cfg
        if framework_config is None:
            framework_config = create_bot_config_layout()
            framework_config.parse_file(config_location, max_index=MAX_PLAYERS)
        if bot_configs is None:
            bot_configs = {}
        if looks_configs is None:
            looks_configs = {}

        match_config = parse_match_config(framework_config, config_location,
                                          bot_configs, looks_configs)
        self.load_match_config(match_config, bot_configs)

    def launch_ball_prediction(self):
        # restart, in case we have changed game mode
        if self.ball_prediction_process:
            self.ball_prediction_process.terminate()

        if self.start_match_configuration.game_mode == 1:  # hoops
            prediction_util.copy_pitch_data_to_temp('hoops')
        elif self.start_match_configuration.game_mode == 2:  # dropshot
            prediction_util.copy_pitch_data_to_temp('dropshot')
        else:
            prediction_util.copy_pitch_data_to_temp('soccar')
        self.ball_prediction_process = prediction_util.launch()

    def launch_bot_processes(self):
        self.logger.debug("Launching bot processes")
        self.kill_sub_processes()

        # Launch processes
        for i in range(self.num_participants):
            if self.start_match_configuration.player_configuration[
                    i].rlbot_controlled:
                queue_holder = self.quick_chat_manager.create_queue_for_bot(
                    i, self.teams[i])
                reload_request = mp.Event()
                quit_callback = mp.Event()
                self.bot_reload_requests.append(reload_request)
                self.bot_quit_callbacks.append(quit_callback)
                process = mp.Process(
                    target=SetupManager.run_agent,
                    args=(self.quit_event, quit_callback, reload_request,
                          self.parameters[i],
                          str(self.start_match_configuration.
                              player_configuration[i].name), self.teams[i], i,
                          self.python_files[i], self.agent_metadata_queue,
                          queue_holder, self.match_config))
                process.start()
                self.sub_processes.append(process)

        self.logger.debug("Successfully started bot processes")

    def launch_quick_chat_manager(self):
        self.quick_chat_manager.start_manager(self.quit_event)
        self.logger.debug("Successfully started quick chat manager")

    def start_match(self):
        self.game_interface.start_match()
        self.logger.info("Match has started")

    def infinite_loop(self):
        instructions = "Press 'r' to reload all agents, or 'q' to exit"
        self.logger.info(instructions)
        while not self.quit_event.is_set():
            # Handle commands
            if msvcrt.kbhit():
                command = msvcrt.getwch()
                if command.lower() == 'r':  # r: reload
                    self.reload_all_agents()
                elif command.lower(
                ) == 'q' or command == '\u001b':  # q or ESC: quit
                    self.shut_down()
                    break
                # Print instructions again if a alphabet character was pressed but no command was found
                elif command.isalpha():
                    self.logger.info(instructions)

            self.try_recieve_agent_metadata()

    def try_recieve_agent_metadata(self):
        """
        Checks whether any of the started bots have posted their AgentMetadata
        yet. If so, we put them on the agent_metadata_map such that we can
        kill their process later when we shut_down(kill_agent_process_ids=True)

        Returns how from how many bots we recieved metadata from.
        """
        num_recieved = 0
        while True:  # will exit on queue.Empty
            try:
                single_agent_metadata = self.agent_metadata_queue.get(
                    timeout=0.1)
                num_recieved += 1
                self.helper_process_manager.start_or_update_helper_process(
                    single_agent_metadata)
                self.agent_metadata_map[
                    single_agent_metadata.index] = single_agent_metadata
                process_configuration.configure_processes(
                    self.agent_metadata_map, self.logger)
            except queue.Empty:
                return num_recieved
            except Exception as ex:
                self.logger.error(ex)
                return num_recieved
        return num_recieved

    def reload_all_agents(self, quiet=False):
        if not quiet:
            self.logger.info("Reloading all agents...")
        for rr in self.bot_reload_requests:
            rr.set()

    def shut_down(self, time_limit=5, kill_all_pids=False, quiet=False):
        if not quiet:
            self.logger.info("Shutting Down")

        self.quit_event.set()
        end_time = datetime.now() + timedelta(seconds=time_limit)
        if self.ball_prediction_process:
            self.ball_prediction_process.terminate()

        # Wait for all processes to terminate before terminating main process
        terminated = False
        while not terminated:
            terminated = True
            for callback in self.bot_quit_callbacks:
                if not callback.is_set():
                    terminated = False
            time.sleep(0.1)
            if datetime.now() > end_time:
                self.logger.info("Taking too long to quit, trying harder...")
                self.kill_sub_processes()
                break

        if kill_all_pids:
            self.kill_agent_process_ids()

        # The quit event can only be set once. Let's reset to our initial state
        self.quit_event = mp.Event()
        self.helper_process_manager = HelperProcessManager(self.quit_event)

        if not quiet:
            self.logger.info("Shut down complete!")

    def load_extension(self, extension_filename):
        try:
            extension_class = import_class_with_base(
                extension_filename, BaseExtension).get_loaded_class()
            self.extension = extension_class(self)
            self.game_interface.set_extension(self.extension)
        except FileNotFoundError as e:
            print(f'Failed to load extension: {e}')

    @staticmethod
    def run_agent(terminate_event, callback_event, reload_request, config_file,
                  name, team, index, python_file, agent_telemetry_queue,
                  queue_holder, match_config: MatchConfig):

        agent_class_wrapper = import_agent(python_file)

        if hasattr(agent_class_wrapper.get_loaded_class(),
                   "run_independently"):
            bm = BotManagerIndependent(terminate_event, callback_event,
                                       reload_request, config_file, name, team,
                                       index, agent_class_wrapper,
                                       agent_telemetry_queue, queue_holder,
                                       match_config)
        elif hasattr(agent_class_wrapper.get_loaded_class(),
                     "get_output_flatbuffer"):
            bm = BotManagerFlatbuffer(terminate_event, callback_event,
                                      reload_request, config_file, name, team,
                                      index, agent_class_wrapper,
                                      agent_telemetry_queue, queue_holder,
                                      match_config)
        else:
            bm = BotManagerStruct(terminate_event, callback_event,
                                  reload_request, config_file, name, team,
                                  index, agent_class_wrapper,
                                  agent_telemetry_queue, queue_holder,
                                  match_config)
        bm.run()

    def kill_sub_processes(self):
        for process in self.sub_processes:
            process.terminate()
        self.sub_processes = []

    def kill_agent_process_ids(self):
        pids = process_configuration.extract_all_pids(self.agent_metadata_map)
        for pid in pids:
            try:
                parent = psutil.Process(pid)
                for child in parent.children(
                        recursive=True
                ):  # or parent.children() for recursive=False
                    self.logger.info(f"Killing {child.pid} (child of {pid})")
                    try:
                        child.kill()
                    except psutil._exceptions.NoSuchProcess:
                        self.logger.info("Already dead.")
                self.logger.info(f"Killing {pid}")
                try:
                    parent.kill()
                except psutil._exceptions.NoSuchProcess:
                    self.logger.info("Already dead.")
            except psutil.NoSuchProcess:
                self.logger.info("Can't fetch parent process, already dead.")
Exemple #30
0
class BotManager:
    def __init__(self, terminate_request_event, termination_complete_event,
                 reload_request_event, bot_configuration, name, team, index,
                 agent_class_wrapper, agent_metadata_queue,
                 match_config: MatchConfig, matchcomms_root: URL):
        """
        :param terminate_request_event: an Event (multiprocessing) which will be set from the outside when the program is trying to terminate
        :param termination_complete_event: an Event (multiprocessing) which should be set from inside this class when termination has completed successfully
        :param reload_request_event: an Event (multiprocessing) which will be set from the outside to force a reload of the agent
        :param reload_complete_event: an Event (multiprocessing) which should be set from inside this class when reloading has completed successfully
        :param bot_configuration: parameters which will be passed to the bot's constructor
        :param name: name which will be passed to the bot's constructor. Will probably be displayed in-game.
        :param team: 0 for blue team or 1 for orange team. Will be passed to the bot's constructor.
        :param index: The player index, i.e. "this is player number <index>". Will be passed to the bot's constructor.
            Can be used to pull the correct data corresponding to the bot's car out of the game tick packet.
        :param agent_class_wrapper: The ExternalClassWrapper object that can be used to load and reload the bot
        :param agent_metadata_queue: a Queue (multiprocessing) which expects to receive AgentMetadata once available.
        :param match_config: Describes the match that is being played.
        :param matchcomms_root: The server to connect to if you want to communicate to other participants in the match.
        """
        self.terminate_request_event = terminate_request_event
        self.termination_complete_event = termination_complete_event
        self.reload_request_event = reload_request_event
        self.bot_configuration = bot_configuration
        self.name = name
        self.team = team
        self.index = index
        self.agent_class_wrapper = agent_class_wrapper
        self.agent_metadata_queue = agent_metadata_queue
        self.logger = get_logger('bot' + str(self.index))
        self.game_interface = GameInterface(self.logger)
        self.last_chat_time = time.time()
        self.chat_counter = 0
        self.reset_chat_time = True
        self.game_tick_packet = None
        self.bot_input = None
        self.ball_prediction = None
        self.rigid_body_tick = None
        self.match_config = match_config
        self.matchcomms_root = matchcomms_root
        self.last_message_index = 0
        self.agent = None
        self.agent_class_file = None
        self.last_module_modification_time = 0

    def send_quick_chat_from_agent(self, team_only, quick_chat):
        """
        Passes the agents quick chats to the game, and also to other python bots.
        This does perform limiting.
        You are limited to 5 quick chats in a 2 second period starting from the first chat.
        This means you can spread your chats out to be even within that 2 second period.
        You could spam them in the first little bit but then will be throttled.
        """

        # Send the quick chat to the game
        rlbot_status = send_quick_chat_flat(self.game_interface, self.index,
                                            self.team, team_only, quick_chat)

        if rlbot_status == RLBotCoreStatus.QuickChatRateExceeded:
            self.logger.debug('quick chat disabled')

    def load_agent(self):
        """
        Loads and initializes an agent using instance variables, registers for quick chat and sets render functions.
        :return: An instance of an agent, and the agent class file.
        """
        agent_class = self.agent_class_wrapper.get_loaded_class()
        self.agent = agent_class(self.name, self.team, self.index)
        self.agent.init_match_config(self.match_config)

        self.agent.load_config(
            self.bot_configuration.get_header("Bot Parameters"))

        self.update_metadata_queue()
        self.set_render_manager()

        self.agent_class_file = self.agent_class_wrapper.python_file
        self.agent._register_quick_chat(self.send_quick_chat_from_agent)
        self.agent._register_field_info(self.get_field_info)
        self.agent._register_set_game_state(self.set_game_state)
        self.agent._register_ball_prediction(self.get_ball_prediction)
        self.agent._register_ball_prediction_struct(
            self.get_ball_prediction_struct)
        self.agent._register_get_rigid_body_tick(self.get_rigid_body_tick)
        self.agent._register_match_settings_func(self.get_match_settings)
        self.agent.matchcomms_root = self.matchcomms_root

        while not self.is_valid_field_info():
            time.sleep(0.1)

        # Once all engine setup is done, do the agent-specific initialization, if any:
        self.agent.initialize_agent()

    def set_render_manager(self):
        """
        Sets the render manager for the agent.
        :param agent: An instance of an agent.
        """
        rendering_manager = self.game_interface.renderer.get_rendering_manager(
            self.index, self.team)
        self.agent._set_renderer(rendering_manager)

    def update_metadata_queue(self):
        """
        Adds a new instance of AgentMetadata into the `agent_metadata_queue` using `agent` data.
        :param agent: An instance of an agent.
        """
        pids = {os.getpid(), *self.agent.get_extra_pids()}

        helper_process_request = self.agent.get_helper_process_request()

        self.agent_metadata_queue.put(
            AgentMetadata(self.index, self.name, self.team, pids,
                          helper_process_request))

    def reload_agent(self):
        """
        Reloads the agent. Can throw exceptions. External classes should use reload_event.set() instead.
        """
        self.logger.info('Reloading Agent: ' + self.agent.name)
        self.agent_class_wrapper.reload()
        old_agent = self.agent
        self.load_agent()
        self.retire_agent(
            old_agent)  # We do this after load_agent as load_agent might fail.

    def run(self):
        """
        Loads interface for RLBot, prepares environment and agent, and calls the update for the agent.
        """
        self.logger.debug('initializing agent')
        self.game_interface.load_interface()

        self.prepare_for_run()

        # Create Ratelimiter
        rate_limit = rate_limiter.RateLimiter(
            GAME_TICK_PACKET_POLLS_PER_SECOND)
        last_tick_game_time = None  # What the tick time of the last observed tick was
        last_call_real_time = datetime.now()  # When we last called the Agent

        # Get bot module
        self.load_agent()

        self.last_module_modification_time = self.check_modification_time(
            os.path.dirname(self.agent_class_file))

        # Run until main process tells to stop, or we detect Ctrl+C
        try:
            while not self.terminate_request_event.is_set():
                self.pull_data_from_game()
                # game_tick_packet = self.game_interface.get
                # Read from game data shared memory

                # Run the Agent only if the game_info has updated.
                tick_game_time = self.get_game_time()
                should_call_while_paused = datetime.now(
                ) - last_call_real_time >= MAX_AGENT_CALL_PERIOD
                if tick_game_time != last_tick_game_time or should_call_while_paused:
                    last_tick_game_time = tick_game_time
                    last_call_real_time = datetime.now()

                    # Reload the Agent if it has been modified or if reload is requested from outside.
                    if self.agent.is_hot_reload_enabled():
                        self.hot_reload_if_necessary()

                    try:
                        chat_messages = self.game_interface.receive_chat(
                            self.index, self.team, self.last_message_index)
                        for i in range(0, chat_messages.MessagesLength()):
                            message = chat_messages.Messages(i)
                            if len(self.match_config.player_configs
                                   ) > message.PlayerIndex():
                                self.agent.handle_quick_chat(
                                    index=message.PlayerIndex(),
                                    team=self.match_config.player_configs[
                                        message.PlayerIndex()].team,
                                    quick_chat=message.QuickChatSelection())
                            else:
                                self.logger.debug(
                                    f"Skipping quick chat delivery for {message.MessageIndex()} because "
                                    "we don't recognize the player index. Probably stale."
                                )
                            self.last_message_index = message.MessageIndex()
                    except EmptyDllResponse:
                        self.logger.debug("Empty response when reading chat!")

                    # Call agent
                    try:
                        self.call_agent(
                            self.agent,
                            self.agent_class_wrapper.get_loaded_class())
                    except Exception as e:
                        self.logger.error("Call to agent failed:\n" +
                                          traceback.format_exc())

                # Ratelimit here
                rate_limit.acquire()
        except KeyboardInterrupt:
            self.terminate_request_event.set()

        self.retire_agent(self.agent)

        # If terminated, send callback
        self.termination_complete_event.set()

    def hot_reload_if_necessary(self):
        try:
            new_module_modification_time = self.check_modification_time(
                os.path.dirname(self.agent_class_file))
            if new_module_modification_time != self.last_module_modification_time or self.reload_request_event.is_set(
            ):
                self.reload_request_event.clear()
                self.last_module_modification_time = new_module_modification_time
                # Clear the render queue on reload.
                if hasattr(self.agent, 'renderer') and isinstance(
                        self.agent.renderer, RenderingManager):
                    self.agent.renderer.clear_all_touched_render_groups()
                self.reload_agent()
        except FileNotFoundError:
            self.logger.error(
                f"Agent file {self.agent_class_file} was not found. Will try again."
            )
            time.sleep(0.5)
        except Exception:
            self.logger.error("Reloading the agent failed:\n" +
                              traceback.format_exc())
            time.sleep(
                5
            )  # Avoid burning CPU, and give the user a moment to read the log

    def retire_agent(self, agent):
        # Shut down the bot by calling cleanup functions.
        if hasattr(agent, 'retire'):
            try:
                agent.retire()
            except Exception as e:
                self.logger.error("Retiring the agent failed:\n" +
                                  traceback.format_exc())
        if hasattr(agent, 'renderer') and isinstance(agent.renderer,
                                                     RenderingManager):
            agent.renderer.clear_all_touched_render_groups()
        # Zero out the inputs, so it's more obvious that the bot has stopped.
        self.game_interface.update_player_input(PlayerInput(), self.index)

        # Don't trust the agent to shut down its own client in retire().
        if agent._matchcomms is not None:
            agent._matchcomms.close()

    @staticmethod
    def check_modification_time(directory):
        files = [f for f in glob.glob(directory + "/**/*.py", recursive=True)]
        max_modification_time = 0
        for file in files:
            mtime = os.stat(file).st_mtime
            if mtime > max_modification_time:
                max_modification_time = mtime
        return max_modification_time

    def get_field_info(self):
        return self.game_interface.get_field_info()

    def get_rigid_body_tick(self):
        """Get the most recent state of the physics engine."""
        return self.game_interface.update_rigid_body_tick(self.rigid_body_tick)

    def set_game_state(self, game_state: GameState) -> None:
        self.game_interface.set_game_state(game_state)

    def get_ball_prediction(self):
        return self.game_interface.get_ball_prediction()

    def get_match_settings(self) -> MatchSettings:
        return self.game_interface.get_match_settings()

    def get_ball_prediction_struct(self):
        raise NotImplementedError

    def prepare_for_run(self):
        raise NotImplementedError

    def call_agent(self, agent: BaseAgent, agent_class):
        raise NotImplementedError

    def get_game_time(self):
        raise NotImplementedError

    def pull_data_from_game(self):
        raise NotImplementedError

    def is_valid_field_info(self) -> bool:
        """Checks if the contents of field info are valid."""
        raise NotImplementedError