# if UNITY_EDITOR || UNITY_STANDALONE_WIN || UNITY_STANDALONE_OSX || UNITY_STANDALONE_LINUX using Grpc.Core; #endif #if UNITY_EDITOR using UnityEditor; #endif using UnityEngine; using MLAgents.CommunicatorObjects; namespace MLAgents { /// Responsible for communication with External using gRPC. public class RpcCommunicator : ICommunicator { /// If true, the communication is active. bool m_IsOpen; # if UNITY_EDITOR || UNITY_STANDALONE_WIN || UNITY_STANDALONE_OSX || UNITY_STANDALONE_LINUX /// The Unity to External client. UnityToExternal.UnityToExternalClient m_Client; #endif /// The communicator parameters sent at construction CommunicatorParameters m_CommunicatorParameters; /// /// Initializes a new instance of the RPCCommunicator class. /// /// Communicator parameters. public RpcCommunicator(CommunicatorParameters communicatorParameters) { m_CommunicatorParameters = communicatorParameters; } /// /// Initialize the communicator by sending the first UnityOutput and receiving the /// first UnityInput. The second UnityInput is stored in the unityInput argument. /// /// The first Unity Input. /// The first Unity Output. /// The second Unity input. public UnityInput Initialize(UnityOutput unityOutput, out UnityInput unityInput) { # if UNITY_EDITOR || UNITY_STANDALONE_WIN || UNITY_STANDALONE_OSX || UNITY_STANDALONE_LINUX m_IsOpen = true; var channel = new Channel( "localhost:" + m_CommunicatorParameters.port, ChannelCredentials.Insecure); m_Client = new UnityToExternal.UnityToExternalClient(channel); var result = m_Client.Exchange(WrapMessage(unityOutput, 200)); unityInput = m_Client.Exchange(WrapMessage(null, 200)).UnityInput; #if UNITY_EDITOR #if UNITY_2017_2_OR_NEWER EditorApplication.playModeStateChanged += HandleOnPlayModeChanged; #else EditorApplication.playmodeStateChanged += HandleOnPlayModeChanged; #endif #endif return result.UnityInput; #else throw new UnityAgentsException( "You cannot perform training on this platform."); #endif } /// /// Close the communicator gracefully on both sides of the communication. /// public void Close() { # if UNITY_EDITOR || UNITY_STANDALONE_WIN || UNITY_STANDALONE_OSX || UNITY_STANDALONE_LINUX if (!m_IsOpen) { return; } try { m_Client.Exchange(WrapMessage(null, 400)); m_IsOpen = false; } catch { // ignored } #else throw new UnityAgentsException( "You cannot perform training on this platform."); #endif } /// /// Send a UnityOutput and receives a UnityInput. /// /// The next UnityInput. /// The UnityOutput to be sent. public UnityInput Exchange(UnityOutput unityOutput) { # if UNITY_EDITOR || UNITY_STANDALONE_WIN || UNITY_STANDALONE_OSX || UNITY_STANDALONE_LINUX if (!m_IsOpen) { return null; } try { var message = m_Client.Exchange(WrapMessage(unityOutput, 200)); if (message.Header.Status == 200) { return message.UnityInput; } else { m_IsOpen = false; return null; } } catch { m_IsOpen = false; return null; } #else throw new UnityAgentsException( "You cannot perform training on this platform."); #endif } /// /// Wraps the UnityOuptut into a message with the appropriate status. /// /// The UnityMessage corresponding. /// The UnityOutput to be wrapped. /// The status of the message. private static UnityMessage WrapMessage(UnityOutput content, int status) { return new UnityMessage { Header = new Header { Status = status }, UnityOutput = content }; } /// /// When the Unity application quits, the communicator must be closed /// private void OnApplicationQuit() { Close(); } #if UNITY_EDITOR #if UNITY_2017_2_OR_NEWER /// /// When the editor exits, the communicator must be closed /// /// State. private void HandleOnPlayModeChanged(PlayModeStateChange state) { // This method is run whenever the playmode state is changed. if (state == PlayModeStateChange.ExitingPlayMode) { Close(); } } #else /// /// When the editor exits, the communicator must be closed /// private void HandleOnPlayModeChanged() { // This method is run whenever the playmode state is changed. if (!EditorApplication.isPlayingOrWillChangePlaymode) { Close(); } } #endif #endif } }