# 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
}
}