Ejemplo n.º 1
0
 def process_side_channel_message(self, data: bytes) -> None:
     """
     Separates the data received from Python into individual messages for each
     registered side channel and calls on_message_received on them.
     :param data: The packed message sent by Unity
     """
     offset = 0
     while offset < len(data):
         try:
             channel_id = uuid.UUID(bytes_le=bytes(data[offset:offset +
                                                        16]))
             offset += 16
             message_len, = struct.unpack_from("<i", data, offset)
             offset = offset + 4
             message_data = data[offset:offset + message_len]
             offset = offset + message_len
         except (struct.error, ValueError, IndexError):
             raise UnityEnvironmentException(
                 "There was a problem reading a message in a SideChannel. "
                 "Please make sure the version of MLAgents in Unity is "
                 "compatible with the Python version.")
         if len(message_data) != message_len:
             raise UnityEnvironmentException(
                 "The message received by the side channel {0} was "
                 "unexpectedly short. Make sure your Unity Environment "
                 "sending side channel data properly.".format(channel_id))
         if channel_id in self._side_channels_dict:
             incoming_message = IncomingMessage(message_data)
             self._side_channels_dict[channel_id].on_message_received(
                 incoming_message)
         else:
             get_logger(__name__).warning(
                 f"Unknown side channel data received. Channel type: {channel_id}."
             )
Ejemplo n.º 2
0
def launch_executable(file_name: str, args: List[str]) -> subprocess.Popen:
    """
    Launches a Unity executable and returns the process handle for it.
    :param file_name: the name of the executable
    :param args: List of string that will be passed as command line arguments
    when launching the executable.
    """
    launch_string = validate_environment_path(file_name)
    if launch_string is None:
        raise UnityEnvironmentException(
            f"Couldn't launch the {file_name} environment. Provided filename does not match any environments."
        )
    else:
        logger.debug(f"The launch string is {launch_string}")
        logger.debug(f"Running with args {args}")
        # Launch Unity environment
        subprocess_args = [launch_string] + args
        try:
            return subprocess.Popen(
                subprocess_args,
                # start_new_session=True means that signals to the parent python process
                # (e.g. SIGINT from keyboard interrupt) will not be sent to the new process on POSIX platforms.
                # This is generally good since we want the environment to have a chance to shutdown,
                # but may be undesirable in come cases; if so, we'll add a command-line toggle.
                # Note that on Windows, the CTRL_C signal will still be sent.
                start_new_session=True,
            )
        except PermissionError as perm:
            # This is likely due to missing read or execute permissions on file.
            raise UnityEnvironmentException(
                f"Error when trying to launch environment - make sure "
                f"permissions are set correctly. For example "
                f'"chmod -R 755 {launch_string}"') from perm
Ejemplo n.º 3
0
 def executable_launcher(self, file_name, no_graphics, args):
     launch_string = self.validate_environment_path(file_name)
     if launch_string is None:
         self._close(0)
         raise UnityEnvironmentException(
             f"Couldn't launch the {file_name} environment. Provided filename does not match any environments."
         )
     else:
         logger.debug("This is the launch string {}".format(launch_string))
         # Launch Unity environment
         subprocess_args = [launch_string]
         if no_graphics:
             subprocess_args += ["-nographics", "-batchmode"]
         subprocess_args += [
             UnityEnvironment.PORT_COMMAND_LINE_ARG,
             str(self.port)
         ]
         subprocess_args += args
         try:
             self.proc1 = subprocess.Popen(
                 subprocess_args,
                 # start_new_session=True means that signals to the parent python process
                 # (e.g. SIGINT from keyboard interrupt) will not be sent to the new process on POSIX platforms.
                 # This is generally good since we want the environment to have a chance to shutdown,
                 # but may be undesirable in come cases; if so, we'll add a command-line toggle.
                 # Note that on Windows, the CTRL_C signal will still be sent.
                 start_new_session=True,
             )
         except PermissionError as perm:
             # This is likely due to missing read or execute permissions on file.
             raise UnityEnvironmentException(
                 f"Error when trying to launch environment - make sure "
                 f"permissions are set correctly. For example "
                 f'"chmod -R 755 {launch_string}"') from perm
Ejemplo n.º 4
0
 def _parse_side_channel_message(side_channels: Dict[uuid.UUID,
                                                     SideChannel],
                                 data: bytes) -> None:
     offset = 0
     while offset < len(data):
         try:
             channel_id = uuid.UUID(bytes_le=bytes(data[offset:offset +
                                                        16]))
             offset += 16
             message_len, = struct.unpack_from("<i", data, offset)
             offset = offset + 4
             message_data = data[offset:offset + message_len]
             offset = offset + message_len
         except Exception:
             raise UnityEnvironmentException(
                 "There was a problem reading a message in a SideChannel. "
                 "Please make sure the version of MLAgents in Unity is "
                 "compatible with the Python version.")
         if len(message_data) != message_len:
             raise UnityEnvironmentException(
                 "The message received by the side channel {0} was "
                 "unexpectedly short. Make sure your Unity Environment "
                 "sending side channel data properly.".format(channel_id))
         if channel_id in side_channels:
             incoming_message = IncomingMessage(message_data)
             side_channels[channel_id].on_message_received(incoming_message)
         else:
             logger.warning(
                 "Unknown side channel data received. Channel type "
                 ": {0}.".format(channel_id))
Ejemplo n.º 5
0
def create_environment_factory(
    env_path: Optional[str],
    no_graphics: bool,
    seed: int,
    start_port: int,
    env_args: Optional[List[str]],
) -> Callable[[int, List[SideChannel]], BaseEnv]:
    if env_path is not None:
        launch_string = UnityEnvironment.validate_environment_path(env_path)
        if launch_string is None:
            raise UnityEnvironmentException(
                f"Couldn't launch the {env_path} environment. Provided filename does not match any environments."
            )

    def create_unity_environment(
        worker_id: int, side_channels: List[SideChannel]
    ) -> UnityEnvironment:
        # Make sure that each environment gets a different seed
        env_seed = seed + worker_id
        return UnityEnvironment(
            file_name=env_path,
            worker_id=worker_id,
            seed=env_seed,
            no_graphics=no_graphics,
            base_port=start_port,
            args=env_args,
            side_channels=side_channels,
        )

    return create_unity_environment
Ejemplo n.º 6
0
 def _raise_version_exception(unity_com_ver: str) -> None:
     raise UnityEnvironmentException(
         f"The communication API version is not compatible between Unity and python. "
         f"Python API: {UnityEnvironment.API_VERSION}, Unity API: {unity_com_ver}.\n "
         f"Please find the versions that work best together from our release page.\n"
         "https://github.com/Unity-Technologies/ml-agents/releases"
     )
Ejemplo n.º 7
0
 def close(self):
     """
     Sends a shutdown signal to the unity environment, and closes the socket connection.
     """
     if self._loaded:
         self._close()
     else:
         raise UnityEnvironmentException("No Unity environment is loaded.")
Ejemplo n.º 8
0
    def failing_env_factory(worker_id, config):
        import time

        # Sleep momentarily to allow time for the EnvManager to be waiting for the
        # subprocess response.  We won't be able to capture failures from the subprocess
        # that cause it to close the pipe before we can send the first message.
        time.sleep(0.1)
        raise UnityEnvironmentException()
Ejemplo n.º 9
0
 def _create_output_path(output_path):
     try:
         if not os.path.exists(output_path):
             os.makedirs(output_path)
     except Exception:
         raise UnityEnvironmentException(
             f"The folder {output_path} containing the "
             "generated model could not be "
             "accessed. Please make sure the "
             "permissions are set correctly.")
Ejemplo n.º 10
0
 def _poll_process(self) -> None:
     """
     Check the status of the subprocess. If it has exited, raise a UnityEnvironmentException
     :return: None
     """
     if not self._process:
         return
     poll_res = self._process.poll()
     if poll_res is not None:
         exc_msg = self._returncode_to_env_message(self._process.returncode)
         raise UnityEnvironmentException(exc_msg)
Ejemplo n.º 11
0
 def reset(self) -> None:
     if self._loaded:
         outputs = self._communicator.exchange(self._generate_reset_input())
         if outputs is None:
             raise UnityCommunicatorStoppedException("Communicator has exited.")
         self._update_behavior_specs(outputs)
         rl_output = outputs.rl_output
         self._update_state(rl_output)
         self._is_first_message = False
         self._env_actions.clear()
     else:
         raise UnityEnvironmentException("No Unity environment is loaded.")
Ejemplo n.º 12
0
def _make_unity_env(
        env_path: Optional[str] = None,
        port: int = UnityEnvironment.BASE_ENVIRONMENT_PORT,
        seed: int = -1,
        env_args: Optional[List[str]] = None,
        engine_config: Optional[EngineConfig] = None,
        side_channels: Optional[List[SideChannel]] = None) -> UnityEnvironment:
    """
    Create a UnityEnvironment.
    """
    # Use Unity Editor if env file is not provided.
    if env_path is None:
        port = UnityEnvironment.DEFAULT_EDITOR_PORT
    else:
        launch_string = UnityEnvironment.validate_environment_path(env_path)
        if launch_string is None:
            raise UnityEnvironmentException(
                f"Couldn't launch the {env_path} environment. Provided filename does not match any environments."
            )
        logger.info(f"Starting environment from {env_path}.")

    # Configure Unity Engine.
    if engine_config is None:
        engine_config = EngineConfig.default_config()

    engine_configuration_channel = EngineConfigurationChannel()
    engine_configuration_channel.set_configuration(engine_config)

    if side_channels is None:
        side_channels = [engine_configuration_channel]
    else:
        side_channels.append(engine_configuration_channel)

    # Find an available port to connect to Unity environment.
    while True:
        try:
            env = UnityEnvironment(
                file_name=env_path,
                seed=seed,
                base_port=port,
                args=env_args,
                side_channels=side_channels,
            )
        except UnityWorkerInUseException:
            logger.debug(f"port {port} in use.")
            port += 1
        else:
            logger.info(f"Connected to environment using port {port}.")
            break

    return env
Ejemplo n.º 13
0
 def _get_side_channels_dict(
     side_channels: Optional[List[SideChannel]]
 ) -> Dict[uuid.UUID, SideChannel]:
     """
     Converts a list of side channels into a dictionary of channel_id to SideChannel
     :param side_channels: The list of side channels.
     """
     side_channels_dict: Dict[uuid.UUID, SideChannel] = {}
     if side_channels is not None:
         for _sc in side_channels:
             if _sc.channel_id in side_channels_dict:
                 raise UnityEnvironmentException(
                     f"There cannot be two side channels with "
                     f"the same channel id {_sc.channel_id}.")
             side_channels_dict[_sc.channel_id] = _sc
     return side_channels_dict
def create_environment_factory_aai(
    env_path: Optional[str],
    # docker_target_name: Optional[str],
    seed: Optional[int],
    start_port: int,
    n_arenas_per_env: int,
    arenas_configurations: ArenaConfig,
    resolution: Optional[int],
) -> Callable[[int, List[SideChannel]], BaseEnv]:
    if env_path is not None:
        launch_string = AnimalAIEnvironment.validate_environment_path(env_path)
        if launch_string is None:
            raise UnityEnvironmentException(
                f"Couldn't launch the {env_path} environment. Provided filename does not match any environments."
            )
    # docker_training = docker_target_name is not None
    # if docker_training and env_path is not None:
    #     #     Comments for future maintenance:
    #     #         Some OS/VM instances (e.g. COS GCP Image) mount filesystems
    #     #         with COS flag which prevents execution of the Unity scene,
    #     #         to get around this, we will copy the executable into the
    #     #         container.
    #     # Navigate in docker path and find env_path and copy it.
    #     env_path = prepare_for_docker_run(docker_target_name, env_path)
    seed_count = 10000
    seed_pool = [np.random.randint(0, seed_count) for _ in range(seed_count)]

    def create_unity_environment(
            worker_id: int,
            side_channels: List[SideChannel]) -> AnimalAIEnvironment:
        env_seed = seed
        if not env_seed:
            env_seed = seed_pool[worker_id % len(seed_pool)]
        return AnimalAIEnvironment(
            file_name=env_path,
            worker_id=worker_id,
            base_port=start_port,
            seed=env_seed,
            # docker_training=docker_training,
            n_arenas=n_arenas_per_env,
            arenas_configurations=arenas_configurations,
            resolution=resolution,
            side_channels=side_channels,
        )

    return create_unity_environment
Ejemplo n.º 15
0
def step_result_to_brain_info(
    step_result: BatchedStepResult,
    group_spec: AgentGroupSpec,
    agent_id_prefix: int = None,
) -> BrainInfo:
    n_agents = step_result.n_agents()
    vis_obs_indices = []
    vec_obs_indices = []
    for index, observation in enumerate(step_result.obs):
        if len(observation.shape) == 2:
            vec_obs_indices.append(index)
        elif len(observation.shape) == 4:
            vis_obs_indices.append(index)
        else:
            raise UnityEnvironmentException(
                "Invalid input received from the environment, the observation should "
                "either be a vector of float or a PNG image")
    if len(vec_obs_indices) == 0:
        vec_obs = np.zeros((n_agents, 0), dtype=np.float32)
    else:
        vec_obs = np.concatenate([step_result.obs[i] for i in vec_obs_indices],
                                 axis=1)
    vis_obs = [step_result.obs[i] for i in vis_obs_indices]
    mask = np.ones((n_agents, np.sum(group_spec.action_size)),
                   dtype=np.float32)
    if group_spec.is_action_discrete():
        mask = np.ones((n_agents, np.sum(group_spec.discrete_action_branches)),
                       dtype=np.float32)
        if step_result.action_mask is not None:
            mask = 1 - np.concatenate(step_result.action_mask, axis=1)
    if agent_id_prefix is None:
        agent_ids = [str(ag_id) for ag_id in list(step_result.agent_id)]
    else:
        agent_ids = [
            f"${agent_id_prefix}-{ag_id}" for ag_id in step_result.agent_id
        ]
    return BrainInfo(
        vis_obs,
        vec_obs,
        list(step_result.reward),
        agent_ids,
        list(step_result.done),
        list(step_result.max_step),
        mask,
    )
Ejemplo n.º 16
0
def create_environment_factory(
    env_path: Optional[str],
    docker_target_name: Optional[str],
    no_graphics: bool,
    seed: Optional[int],
    start_port: int,
    env_args: Optional[List[str]],
) -> Callable[[int, List[SideChannel]], BaseEnv]:
    if env_path is not None:
        launch_string = UnityEnvironment.validate_environment_path(env_path)
        if launch_string is None:
            raise UnityEnvironmentException(
                f"Couldn't launch the {env_path} environment. Provided filename does not match any environments."
            )
    docker_training = docker_target_name is not None
    if docker_training and env_path is not None:
        #     Comments for future maintenance:
        #         Some OS/VM instances (e.g. COS GCP Image) mount filesystems
        #         with COS flag which prevents execution of the Unity scene,
        #         to get around this, we will copy the executable into the
        #         container.
        # Navigate in docker path and find env_path and copy it.
        env_path = prepare_for_docker_run(docker_target_name, env_path)
    seed_count = 10000
    seed_pool = [np.random.randint(0, seed_count) for _ in range(seed_count)]

    def create_unity_environment(
            worker_id: int,
            side_channels: List[SideChannel]) -> UnityEnvironment:
        env_seed = seed
        if not env_seed:
            env_seed = seed_pool[worker_id % len(seed_pool)]
        return UnityEnvironment(
            file_name=env_path,
            worker_id=worker_id,
            seed=env_seed,
            docker_training=docker_training,
            no_graphics=no_graphics,
            base_port=start_port,
            args=env_args,
            side_channels=side_channels,
        )

    return create_unity_environment
Ejemplo n.º 17
0
 def step(self) -> None:
     if self._is_first_message:
         return self.reset()
     if not self._loaded:
         raise UnityEnvironmentException("No Unity environment is loaded.")
     # fill the blanks for missing actions
     for group_name in self._env_specs:
         if group_name not in self._env_actions:
             n_agents = 0
             if group_name in self._env_state:
                 n_agents = len(self._env_state[group_name][0])
             self._env_actions[group_name] = self._env_specs[
                 group_name].create_empty_action(n_agents)
     step_input = self._generate_step_input(self._env_actions)
     with hierarchical_timer("communicator.exchange"):
         outputs = self.communicator.exchange(step_input)
     if outputs is None:
         raise UnityCommunicationException("Communicator has stopped.")
     self._update_behavior_specs(outputs)
     rl_output = outputs.rl_output
     self._update_state(rl_output)
     self._env_actions.clear()
Ejemplo n.º 18
0
    def __init__(
        self,
        file_name: Optional[str] = None,
        worker_id: int = 0,
        base_port: Optional[int] = None,
        seed: int = 0,
        no_graphics: bool = False,
        timeout_wait: int = 60,
        additional_args: Optional[List[str]] = None,
        side_channels: Optional[List[SideChannel]] = None,
        log_folder: Optional[str] = None,
    ):
        """
        Starts a new unity environment and establishes a connection with the environment.
        Notice: Currently communication between Unity and Python takes place over an open socket without authentication.
        Ensure that the network where training takes place is secure.

        :string file_name: Name of Unity environment binary.
        :int base_port: Baseline port number to connect to Unity environment over. worker_id increments over this.
        If no environment is specified (i.e. file_name is None), the DEFAULT_EDITOR_PORT will be used.
        :int worker_id: Offset from base_port. Used for training multiple environments simultaneously.
        :bool no_graphics: Whether to run the Unity simulator in no-graphics mode
        :int timeout_wait: Time (in seconds) to wait for connection from environment.
        :list args: Addition Unity command line arguments
        :list side_channels: Additional side channel for no-rl communication with Unity
        :str log_folder: Optional folder to write the Unity Player log file into.  Requires absolute path.
        """
        atexit.register(self._close)
        self._additional_args = additional_args or []
        self._no_graphics = no_graphics
        # If base port is not specified, use BASE_ENVIRONMENT_PORT if we have
        # an environment, otherwise DEFAULT_EDITOR_PORT
        if base_port is None:
            base_port = (
                self.BASE_ENVIRONMENT_PORT if file_name else self.DEFAULT_EDITOR_PORT
            )
        self._port = base_port + worker_id
        self._buffer_size = 12000
        # If true, this means the environment was successfully loaded
        self._loaded = False
        # The process that is started. If None, no process was started
        self._proc1 = None
        self._timeout_wait: int = timeout_wait
        self._communicator = self._get_communicator(worker_id, base_port, timeout_wait)
        self._worker_id = worker_id
        self._side_channel_manager = SideChannelManager(side_channels)
        self._log_folder = log_folder

        # If the environment name is None, a new environment will not be launched
        # and the communicator will directly try to connect to an existing unity environment.
        # If the worker-id is not 0 and the environment name is None, an error is thrown
        if file_name is None and worker_id != 0:
            raise UnityEnvironmentException(
                "If the environment name is None, "
                "the worker-id must be 0 in order to connect with the Editor."
            )
        if file_name is not None:
            try:
                self._proc1 = env_utils.launch_executable(
                    file_name, self._executable_args()
                )
            except UnityEnvironmentException:
                self._close(0)
                raise
        else:
            logger.info(
                f"Listening on port {self._port}. "
                f"Start training by pressing the Play button in the Unity Editor."
            )
        self._loaded = True

        rl_init_parameters_in = UnityRLInitializationInputProto(
            seed=seed,
            communication_version=self.API_VERSION,
            package_version=mlagents_envs.__version__,
            capabilities=UnityEnvironment._get_capabilities_proto(),
        )
        try:
            aca_output = self._send_academy_parameters(rl_init_parameters_in)
            aca_params = aca_output.rl_initialization_output
        except UnityTimeOutException:
            self._close(0)
            raise

        if not UnityEnvironment._check_communication_compatibility(
            aca_params.communication_version,
            UnityEnvironment.API_VERSION,
            aca_params.package_version,
        ):
            self._close(0)
            UnityEnvironment._raise_version_exception(aca_params.communication_version)

        UnityEnvironment._warn_csharp_base_capabilities(
            aca_params.capabilities,
            aca_params.package_version,
            UnityEnvironment.API_VERSION,
        )

        self._env_state: Dict[str, Tuple[DecisionSteps, TerminalSteps]] = {}
        self._env_specs: Dict[str, BehaviorSpec] = {}
        self._env_actions: Dict[str, np.ndarray] = {}
        self._is_first_message = True
        self._update_behavior_specs(aca_output)
Ejemplo n.º 19
0
 def _raise_version_exception(unity_com_ver: str) -> None:
     raise UnityEnvironmentException(
         f"The communication API version is not compatible between Unity and python. "
         f"Python API: {UnityEnvironment.API_VERSION}, Unity API: {unity_com_ver}.\n "
         f"Please go to https://github.com/Unity-Technologies/ml-agents/releases/tag/latest_release "
         f"to download the latest version of ML-Agents.")
Ejemplo n.º 20
0
    def executable_launcher(self, file_name, docker_training, no_graphics,
                            args):
        launch_string = self.validate_environment_path(file_name)
        if launch_string is None:
            self._close(0)
            raise UnityEnvironmentException(
                f"Couldn't launch the {file_name} environment. Provided filename does not match any environments."
            )
        else:
            logger.debug("This is the launch string {}".format(launch_string))
            # Launch Unity environment
            if not docker_training:
                subprocess_args = [launch_string]
                if no_graphics:
                    subprocess_args += ["-nographics", "-batchmode"]
                subprocess_args += [
                    UnityEnvironment.PORT_COMMAND_LINE_ARG,
                    str(self.port),
                ]
                subprocess_args += args
                try:
                    self.proc1 = subprocess.Popen(
                        subprocess_args,
                        # start_new_session=True means that signals to the parent python process
                        # (e.g. SIGINT from keyboard interrupt) will not be sent to the new process on POSIX platforms.
                        # This is generally good since we want the environment to have a chance to shutdown,
                        # but may be undesirable in come cases; if so, we'll add a command-line toggle.
                        # Note that on Windows, the CTRL_C signal will still be sent.
                        start_new_session=True,
                    )
                except PermissionError as perm:
                    # This is likely due to missing read or execute permissions on file.
                    raise UnityEnvironmentException(
                        f"Error when trying to launch environment - make sure "
                        f"permissions are set correctly. For example "
                        f'"chmod -R 755 {launch_string}"') from perm

            else:
                # Comments for future maintenance:
                #     xvfb-run is a wrapper around Xvfb, a virtual xserver where all
                #     rendering is done to virtual memory. It automatically creates a
                #     new virtual server automatically picking a server number `auto-servernum`.
                #     The server is passed the arguments using `server-args`, we are telling
                #     Xvfb to create Screen number 0 with width 640, height 480 and depth 24 bits.
                #     Note that 640 X 480 are the default width and height. The main reason for
                #     us to add this is because we'd like to change the depth from the default
                #     of 8 bits to 24.
                #     Unfortunately, this means that we will need to pass the arguments through
                #     a shell which is why we set `shell=True`. Now, this adds its own
                #     complications. E.g SIGINT can bounce off the shell and not get propagated
                #     to the child processes. This is why we add `exec`, so that the shell gets
                #     launched, the arguments are passed to `xvfb-run`. `exec` replaces the shell
                #     we created with `xvfb`.
                #
                docker_ls = (
                    f"exec xvfb-run --auto-servernum --server-args='-screen 0 640x480x24'"
                    f" {launch_string} {UnityEnvironment.PORT_COMMAND_LINE_ARG} {self.port}"
                )

                self.proc1 = subprocess.Popen(
                    docker_ls,
                    stdout=subprocess.PIPE,
                    stderr=subprocess.PIPE,
                    shell=True,
                )
Ejemplo n.º 21
0
    def __init__(
        self,
        file_name: Optional[str] = None,
        worker_id: int = 0,
        base_port: Optional[int] = None,
        seed: int = 0,
        no_graphics: bool = False,
        timeout_wait: int = 60,
        args: Optional[List[str]] = None,
        side_channels: Optional[List[SideChannel]] = None,
    ):
        """
        Starts a new unity environment and establishes a connection with the environment.
        Notice: Currently communication between Unity and Python takes place over an open socket without authentication.
        Ensure that the network where training takes place is secure.

        :string file_name: Name of Unity environment binary.
        :int base_port: Baseline port number to connect to Unity environment over. worker_id increments over this.
        If no environment is specified (i.e. file_name is None), the DEFAULT_EDITOR_PORT will be used.
        :int worker_id: Offset from base_port. Used for training multiple environments simultaneously.
        :bool no_graphics: Whether to run the Unity simulator in no-graphics mode
        :int timeout_wait: Time (in seconds) to wait for connection from environment.
        :list args: Addition Unity command line arguments
        :list side_channels: Additional side channel for no-rl communication with Unity
        """
        args = args or []
        atexit.register(self._close)
        # If base port is not specified, use BASE_ENVIRONMENT_PORT if we have
        # an environment, otherwise DEFAULT_EDITOR_PORT
        if base_port is None:
            base_port = (self.BASE_ENVIRONMENT_PORT
                         if file_name else self.DEFAULT_EDITOR_PORT)
        self.port = base_port + worker_id
        self._buffer_size = 12000
        # If true, this means the environment was successfully loaded
        self._loaded = False
        # The process that is started. If None, no process was started
        self.proc1 = None
        self.timeout_wait: int = timeout_wait
        self.communicator = self.get_communicator(worker_id, base_port,
                                                  timeout_wait)
        self.worker_id = worker_id
        self.side_channels: Dict[uuid.UUID, SideChannel] = {}
        if side_channels is not None:
            for _sc in side_channels:
                if _sc.channel_id in self.side_channels:
                    raise UnityEnvironmentException(
                        "There cannot be two side channels with the same channel id {0}."
                        .format(_sc.channel_id))
                self.side_channels[_sc.channel_id] = _sc

        # If the environment name is None, a new environment will not be launched
        # and the communicator will directly try to connect to an existing unity environment.
        # If the worker-id is not 0 and the environment name is None, an error is thrown
        if file_name is None and worker_id != 0:
            raise UnityEnvironmentException(
                "If the environment name is None, "
                "the worker-id must be 0 in order to connect with the Editor.")
        if file_name is not None:
            self.executable_launcher(file_name, no_graphics, args)
        else:
            logger.info(
                f"Listening on port {self.port}. "
                f"Start training by pressing the Play button in the Unity Editor."
            )
        self._loaded = True

        rl_init_parameters_in = UnityRLInitializationInputProto(
            seed=seed,
            communication_version=self.API_VERSION,
            package_version=mlagents_envs.__version__,
        )
        try:
            aca_output = self.send_academy_parameters(rl_init_parameters_in)
            aca_params = aca_output.rl_initialization_output
        except UnityTimeOutException:
            self._close(0)
            raise

        unity_communicator_version = aca_params.communication_version
        if unity_communicator_version != UnityEnvironment.API_VERSION:
            self._close(0)
            raise UnityEnvironmentException(
                f"The communication API version is not compatible between Unity and python. "
                f"Python API: {UnityEnvironment.API_VERSION}, Unity API: {unity_communicator_version}.\n "
                f"Please go to https://github.com/Unity-Technologies/ml-agents/releases/tag/latest_release "
                f"to download the latest version of ML-Agents.")
        else:
            logger.info(
                f"Connected to Unity environment with package version {aca_params.package_version} "
                f"and communication version {aca_params.communication_version}"
            )
        self._env_state: Dict[str, BatchedStepResult] = {}
        self._env_specs: Dict[str, AgentGroupSpec] = {}
        self._env_actions: Dict[str, np.ndarray] = {}
        self._is_first_message = True
        self._update_group_specs(aca_output)
Ejemplo n.º 22
0
    def __init__(
        self,
        file_name: Optional[str] = None,
        worker_id: int = 0,
        base_port: int = 5005,
        seed: int = 0,
        docker_training: bool = False,
        no_graphics: bool = False,
        timeout_wait: int = 60,
        args: Optional[List[str]] = None,
        side_channels: Optional[List[SideChannel]] = None,
    ):
        """
        Starts a new unity environment and establishes a connection with the environment.
        Notice: Currently communication between Unity and Python takes place over an open socket without authentication.
        Ensure that the network where training takes place is secure.

        :string file_name: Name of Unity environment binary.
        :int base_port: Baseline port number to connect to Unity environment over. worker_id increments over this.
        :int worker_id: Number to add to communication port (5005) [0]. Used for asynchronous agent scenarios.
        :bool docker_training: Informs this class whether the process is being run within a container.
        :bool no_graphics: Whether to run the Unity simulator in no-graphics mode
        :int timeout_wait: Time (in seconds) to wait for connection from environment.
        :bool train_mode: Whether to run in training mode, speeding up the simulation, by default.
        :list args: Addition Unity command line arguments
        :list side_channels: Additional side channel for no-rl communication with Unity
        """
        args = args or []
        atexit.register(self._close)
        self.port = base_port + worker_id
        self._buffer_size = 12000
        self._version_ = UnityEnvironment.API_VERSION
        # If true, this means the environment was successfully loaded
        self._loaded = False
        # The process that is started. If None, no process was started
        self.proc1 = None
        self.timeout_wait: int = timeout_wait
        self.communicator = self.get_communicator(worker_id, base_port,
                                                  timeout_wait)
        self.worker_id = worker_id
        self.side_channels: Dict[int, SideChannel] = {}
        if side_channels is not None:
            for _sc in side_channels:
                if _sc.channel_type in self.side_channels:
                    raise UnityEnvironmentException(
                        "There cannot be two side channels with the same channel type {0}."
                        .format(_sc.channel_type))
                self.side_channels[_sc.channel_type] = _sc

        # If the environment name is None, a new environment will not be launched
        # and the communicator will directly try to connect to an existing unity environment.
        # If the worker-id is not 0 and the environment name is None, an error is thrown
        if file_name is None and worker_id != 0:
            raise UnityEnvironmentException(
                "If the environment name is None, "
                "the worker-id must be 0 in order to connect with the Editor.")
        if file_name is not None:
            self.executable_launcher(file_name, docker_training, no_graphics,
                                     args)
        else:
            logger.info(
                f"Listening on port {self.port}. "
                f"Start training by pressing the Play button in the Unity Editor."
            )
        self._loaded = True

        rl_init_parameters_in = UnityRLInitializationInputProto(seed=seed)
        try:
            aca_output = self.send_academy_parameters(rl_init_parameters_in)
            aca_params = aca_output.rl_initialization_output
        except UnityTimeOutException:
            self._close()
            raise
        # TODO : think of a better way to expose the academyParameters
        self._unity_version = aca_params.version
        if self._unity_version != self._version_:
            self._close()
            raise UnityEnvironmentException(
                f"The API number is not compatible between Unity and python. "
                f"Python API: {self._version_}, Unity API: {self._unity_version}.\n"
                f"Please go to https://github.com/Unity-Technologies/ml-agents/releases/tag/latest_release"
                f"to download the latest version of ML-Agents.")
        self._env_state: Dict[str, BatchedStepResult] = {}
        self._env_specs: Dict[str, AgentGroupSpec] = {}
        self._env_actions: Dict[str, np.ndarray] = {}
        self._is_first_message = True
        self._update_group_specs(aca_output)
Ejemplo n.º 23
0
    def executable_launcher(self, file_name, docker_training, no_graphics,
                            args):
        cwd = os.getcwd()
        file_name = (file_name.strip().replace(".app", "").replace(
            ".exe", "").replace(".x86_64", "").replace(".x86", ""))
        true_filename = os.path.basename(os.path.normpath(file_name))
        logger.debug("The true file name is {}".format(true_filename))
        launch_string = None
        if platform == "linux" or platform == "linux2":
            candidates = glob.glob(os.path.join(cwd, file_name) + ".x86_64")
            if len(candidates) == 0:
                candidates = glob.glob(os.path.join(cwd, file_name) + ".x86")
            if len(candidates) == 0:
                candidates = glob.glob(file_name + ".x86_64")
            if len(candidates) == 0:
                candidates = glob.glob(file_name + ".x86")
            if len(candidates) > 0:
                launch_string = candidates[0]

        elif platform == "darwin":
            candidates = glob.glob(
                os.path.join(cwd, file_name + ".app", "Contents", "MacOS",
                             true_filename))
            if len(candidates) == 0:
                candidates = glob.glob(
                    os.path.join(file_name + ".app", "Contents", "MacOS",
                                 true_filename))
            if len(candidates) == 0:
                candidates = glob.glob(
                    os.path.join(cwd, file_name + ".app", "Contents", "MacOS",
                                 "*"))
            if len(candidates) == 0:
                candidates = glob.glob(
                    os.path.join(file_name + ".app", "Contents", "MacOS", "*"))
            if len(candidates) > 0:
                launch_string = candidates[0]
        elif platform == "win32":
            candidates = glob.glob(os.path.join(cwd, file_name + ".exe"))
            if len(candidates) == 0:
                candidates = glob.glob(file_name + ".exe")
            if len(candidates) > 0:
                launch_string = candidates[0]
        if launch_string is None:
            self._close()
            raise UnityEnvironmentException(
                "Couldn't launch the {0} environment. "
                "Provided filename does not match any environments.".format(
                    true_filename))
        else:
            logger.debug("This is the launch string {}".format(launch_string))
            # Launch Unity environment
            if not docker_training:
                subprocess_args = [launch_string]
                if no_graphics:
                    subprocess_args += ["-nographics", "-batchmode"]
                subprocess_args += ["--port", str(self.port)]
                subprocess_args += args
                try:
                    self.proc1 = subprocess.Popen(
                        subprocess_args,
                        # start_new_session=True means that signals to the parent python process
                        # (e.g. SIGINT from keyboard interrupt) will not be sent to the new process on POSIX platforms.
                        # This is generally good since we want the environment to have a chance to shutdown,
                        # but may be undesirable in come cases; if so, we'll add a command-line toggle.
                        # Note that on Windows, the CTRL_C signal will still be sent.
                        start_new_session=True,
                    )
                except PermissionError as perm:
                    # This is likely due to missing read or execute permissions on file.
                    raise UnityEnvironmentException(
                        f"Error when trying to launch environment - make sure "
                        f"permissions are set correctly. For example "
                        f'"chmod -R 755 {launch_string}"') from perm

            else:
                # Comments for future maintenance:
                #     xvfb-run is a wrapper around Xvfb, a virtual xserver where all
                #     rendering is done to virtual memory. It automatically creates a
                #     new virtual server automatically picking a server number `auto-servernum`.
                #     The server is passed the arguments using `server-args`, we are telling
                #     Xvfb to create Screen number 0 with width 640, height 480 and depth 24 bits.
                #     Note that 640 X 480 are the default width and height. The main reason for
                #     us to add this is because we'd like to change the depth from the default
                #     of 8 bits to 24.
                #     Unfortunately, this means that we will need to pass the arguments through
                #     a shell which is why we set `shell=True`. Now, this adds its own
                #     complications. E.g SIGINT can bounce off the shell and not get propagated
                #     to the child processes. This is why we add `exec`, so that the shell gets
                #     launched, the arguments are passed to `xvfb-run`. `exec` replaces the shell
                #     we created with `xvfb`.
                #
                docker_ls = ("exec xvfb-run --auto-servernum"
                             " --server-args='-screen 0 640x480x24'"
                             " {0} --port {1}").format(launch_string,
                                                       str(self.port))
                self.proc1 = subprocess.Popen(
                    docker_ls,
                    stdout=subprocess.PIPE,
                    stderr=subprocess.PIPE,
                    shell=True,
                )
Ejemplo n.º 24
0
    def __init__(
        self,
        file_name: Optional[str] = None,
        worker_id: int = 0,
        base_port: Optional[int] = None,
        seed: int = 0,
        no_graphics: bool = False,
        timeout_wait: int = 60,
        additional_args: Optional[List[str]] = None,
        side_channels: Optional[List[SideChannel]] = None,
        log_folder: Optional[str] = None,
    ):
        """
        Starts a new unity environment and establishes a connection with the environment.
        Notice: Currently communication between Unity and Python takes place over an open socket without authentication.
        Ensure that the network where training takes place is secure.

        Args:
            file_name: The file path to the environment.
            worker_id: The index of the worker in the environment.
            base_port: The base port to use for running the environment.
            seed: The seed to use for running the environment.
            no_graphics: Whether to run the environment in no-graphics mode.
            timeout_wait: Timeout for connecting to the environment.
            additional_args: Additional command line arguments to pass to the Unity executable.
            side_channels: List of side channel to register.
            log_folder: The folder to save the Unity executable to.

        Raises:
            UnityEnvironmentException: If file_name does not correspond to a valid environment.
        """
        if side_channels is None:
            side_channels = [
                EnvironmentParametersChannel(),
                EngineConfigurationChannel(),
            ]

        for sc in side_channels:
            if isinstance(sc, EnvironmentParametersChannel):
                self._env_parameters_channel = sc
            elif isinstance(sc, EngineConfigurationChannel):
                self._engine_configuration_channel = sc

        super().__init__(
            file_name,
            worker_id,
            base_port,
            seed,
            no_graphics,
            timeout_wait,
            additional_args,
            side_channels,
            log_folder,
        )
        if not self.behavior_specs:
            super().reset()
        self.name = list(self.behavior_specs.keys())[0]
        self.group_spec = self.behavior_specs[self.name]

        super().reset()
        decision_steps, _ = self.get_steps(self.name)
        self._previous_decision_step = decision_steps

        # Set action spaces
        if self.group_spec.action_spec.is_discrete():
            self.action_size = self.group_spec.action_spec.discrete_size
            branches = self.group_spec.action_spec.discrete_branches
            self._action_spec = branches[0]
        elif self.group_spec.action_spec.is_continuous():
            self.action_size = self.group_spec.action_spec.continuous_size
        else:
            raise UnityEnvironmentException(
                "The environment does not contain a valid action space.")

        # Set observations spaces
        observation_spec = []
        shapes = self._get_vis_obs_shape()
        observation_spec.append(shapes[0])  # (64, 64, 3)
        if self._get_vec_obs_size() > 0:
            observation_spec.append((self._get_vec_obs_size(), ))  # (2,)
        self._observation_spec = observation_spec