using UnityEngine;
using System.Collections.Generic;
using UnityEngine.Serialization;
#if UNITY_EDITOR
using UnityEditor;
#endif
using MLAgents.InferenceBrain;
using Barracuda;
/**
* Welcome to Unity Machine Learning Agents (ML-Agents).
*
* The ML-Agents toolkit contains four entities: Academy, Agent, Communicator and
* Python API. The academy and connected agents live within
* a learning environment (herein called Environment), while the communicator
* manages the communication between the learning environment and the Python
* API. For more information on each of these entities, in addition to how to
* set-up a learning environment and train the behavior of characters in a
* Unity scene, please browse our documentation pages on GitHub:
* https://github.com/Unity-Technologies/ml-agents/blob/master/docs/
*/
namespace MLAgents
{
///
/// An Academy is where Agent objects go to train their behaviors.
/// Currently, this class is expected to be extended to
/// implement the desired academy behavior.
///
///
/// When an academy is run, it can either be in inference or training mode.
/// The mode is determined by the presence or absence of a Communicator. In
/// the presence of a communicator, the academy is run in training mode where
/// the states and observations of each agent are sent through the
/// communicator. In the absence of a communicator, the academy is run in
/// inference mode where the agent behavior is determined by the Policy
/// attached to it.
///
[HelpURL("https://github.com/Unity-Technologies/ml-agents/blob/master/" +
"docs/Learning-Environment-Design-Academy.md")]
public abstract class Academy : MonoBehaviour
{
const string k_ApiVersion = "API-12";
/// Temporary storage for global gravity value
/// Used to restore oringal value when deriving Academy modifies it
Vector3 m_OriginalGravity;
/// Temporary storage for global fixedDeltaTime value
/// Used to restore original value when deriving Academy modifies it
float m_OriginalFixedDeltaTime;
/// Temporary storage for global maximumDeltaTime value
/// Used to restore original value when deriving Academy modifies it
float m_OriginalMaximumDeltaTime;
public IFloatProperties FloatProperties;
// Fields not provided in the Inspector.
///
/// Returns whether or not the communicator is on.
///
///
/// true, if communicator is on, false otherwise.
///
public bool IsCommunicatorOn
{
get { return Communicator != null; }
}
/// The number of episodes completed by the environment. Incremented
/// each time the environment is reset.
int m_EpisodeCount;
/// The number of steps completed within the current episode. Incremented
/// each time a step is taken in the environment. Is reset to 0 during
/// .
int m_StepCount;
/// The number of total number of steps completed during the whole simulation. Incremented
/// each time a step is taken in the environment.
int m_TotalStepCount;
/// Pointer to the communicator currently in use by the Academy.
public ICommunicator Communicator;
bool m_Initialized;
List m_ModelRunners = new List();
// Flag used to keep track of the first time the Academy is reset.
bool m_FirstAcademyReset;
// The Academy uses a series of events to communicate with agents
// to facilitate synchronization. More specifically, it ensure
// that all the agents performs their steps in a consistent order (i.e. no
// agent can act based on a decision before another agent has had a chance
// to request a decision).
// Signals to all the Agents at each environment step so they can use
// their Policy to decide on their next action.
public event System.Action DecideAction;
// Signals to all the listeners that the academy is being destroyed
public event System.Action DestroyAction;
// Signals to all the agents at each environment step along with the
// Academy's maxStepReached, done and stepCount values. The agents rely
// on this event to update their own values of max step reached and done
// in addition to aligning on the step count of the global episode.
public event System.Action AgentSetStatus;
// Signals to all the agents at each environment step so they can reset
// if their flag has been set to done (assuming the agent has requested a
// decision).
public event System.Action AgentResetIfDone;
// Signals to all the agents at each environment step so they can send
// their state to their Policy if they have requested a decision.
public event System.Action AgentSendState;
// Signals to all the agents at each environment step so they can act if
// they have requested a decision.
public event System.Action AgentAct;
// Signals to all the agents each time the Academy force resets.
public event System.Action AgentForceReset;
///
/// MonoBehavior function called at the very beginning of environment
/// creation. Academy uses this time to initialize internal data
/// structures, initialize the environment and check for the existence
/// of a communicator.
///
void Awake()
{
LazyInitialization();
}
public void LazyInitialization()
{
if (!m_Initialized)
{
InitializeEnvironment();
m_Initialized = true;
}
}
// Used to read Python-provided environment parameters
static int ReadArgs()
{
var args = System.Environment.GetCommandLineArgs();
var inputPort = "";
for (var i = 0; i < args.Length; i++)
{
if (args[i] == "--port")
{
inputPort = args[i + 1];
}
}
return int.Parse(inputPort);
}
///
/// Initializes the environment, configures it and initialized the Academy.
///
void InitializeEnvironment()
{
m_OriginalGravity = Physics.gravity;
m_OriginalFixedDeltaTime = Time.fixedDeltaTime;
m_OriginalMaximumDeltaTime = Time.maximumDeltaTime;
var floatProperties = new FloatPropertiesChannel();
FloatProperties = floatProperties;
InitializeAcademy();
// Try to launch the communicator by using the arguments passed at launch
try
{
Communicator = new RpcCommunicator(
new CommunicatorInitParameters
{
port = ReadArgs()
});
}
catch
{
#if UNITY_EDITOR
Communicator = new RpcCommunicator(
new CommunicatorInitParameters
{
port = 5004
});
#endif
}
if (Communicator != null)
{
Communicator.RegisterSideChannel(new EngineConfigurationChannel());
Communicator.RegisterSideChannel(floatProperties);
// We try to exchange the first message with Python. If this fails, it means
// no Python Process is ready to train the environment. In this case, the
//environment must use Inference.
try
{
var unityRLInitParameters = Communicator.Initialize(
new CommunicatorInitParameters
{
version = k_ApiVersion,
name = gameObject.name,
});
Random.InitState(unityRLInitParameters.seed);
}
catch
{
Communicator = null;
}
if (Communicator != null)
{
Communicator.QuitCommandReceived += OnQuitCommandReceived;
Communicator.ResetCommandReceived += OnResetCommand;
}
}
// If a communicator is enabled/provided, then we assume we are in
// training mode. In the absence of a communicator, we assume we are
// in inference mode.
DecideAction += () => { };
DestroyAction += () => { };
AgentSetStatus += i => { };
AgentResetIfDone += () => { };
AgentSendState += () => { };
AgentAct += () => { };
AgentForceReset += () => { };
}
static void OnQuitCommandReceived()
{
#if UNITY_EDITOR
EditorApplication.isPlaying = false;
#endif
Application.Quit();
}
void OnResetCommand()
{
ForcedFullReset();
}
///
/// Initializes the academy and environment. Called during the waking-up
/// phase of the environment before any of the scene objects/agents have
/// been initialized.
///
public virtual void InitializeAcademy()
{
}
///
/// Specifies the academy behavior at every step of the environment.
///
public virtual void AcademyStep()
{
}
///
/// Specifies the academy behavior when being reset (i.e. at the completion
/// of a global episode).
///
public virtual void AcademyReset()
{
}
///
/// Returns the current episode counter.
///
///
/// Current episode number.
///
public int GetEpisodeCount()
{
return m_EpisodeCount;
}
///
/// Returns the current step counter (within the current episode).
///
///
/// Current step count.
///
public int GetStepCount()
{
return m_StepCount;
}
///
/// Returns the total step counter.
///
///
/// Total step count.
///
public int GetTotalStepCount()
{
return m_TotalStepCount;
}
///
/// Forces the full reset. The done flags are not affected. Is either
/// called the first reset at inference and every external reset
/// at training.
///
void ForcedFullReset()
{
EnvironmentReset();
AgentForceReset?.Invoke();
m_FirstAcademyReset = true;
}
///
/// Performs a single environment update to the Academy, and Agent
/// objects within the environment.
///
void EnvironmentStep()
{
if (!m_FirstAcademyReset)
{
ForcedFullReset();
}
AgentSetStatus?.Invoke(m_StepCount);
using (TimerStack.Instance.Scoped("AgentResetIfDone"))
{
AgentResetIfDone?.Invoke();
}
using (TimerStack.Instance.Scoped("AgentSendState"))
{
AgentSendState?.Invoke();
}
using (TimerStack.Instance.Scoped("DecideAction"))
{
DecideAction?.Invoke();
}
using (TimerStack.Instance.Scoped("AcademyStep"))
{
AcademyStep();
}
using (TimerStack.Instance.Scoped("AgentAct"))
{
AgentAct?.Invoke();
}
m_StepCount += 1;
m_TotalStepCount += 1;
}
///
/// Resets the environment, including the Academy.
///
void EnvironmentReset()
{
m_StepCount = 0;
m_EpisodeCount++;
AcademyReset();
}
///
/// MonoBehaviour function that dictates each environment step.
///
void FixedUpdate()
{
EnvironmentStep();
}
///
/// Creates or retrieves an existing ModelRunner that uses the same
/// NNModel and the InferenceDevice as provided.
///
/// The NNModel the ModelRunner must use
/// The brainParameters used to create
/// the ModelRunner
/// The inference device (CPU or GPU)
/// the ModelRunner will use
/// The ModelRunner compatible with the input settings
public ModelRunner GetOrCreateModelRunner(
NNModel model, BrainParameters brainParameters, InferenceDevice inferenceDevice)
{
var modelRunner = m_ModelRunners.Find(x => x.HasModel(model, inferenceDevice));
if (modelRunner == null)
{
modelRunner = new ModelRunner(
model, brainParameters, inferenceDevice);
m_ModelRunners.Add(modelRunner);
}
return modelRunner;
}
///
/// Cleanup function
///
protected virtual void OnDestroy()
{
Physics.gravity = m_OriginalGravity;
Time.fixedDeltaTime = m_OriginalFixedDeltaTime;
Time.maximumDeltaTime = m_OriginalMaximumDeltaTime;
// Signal to listeners that the academy is being destroyed now
DestroyAction?.Invoke();
foreach (var mr in m_ModelRunners)
{
mr.Dispose();
}
// TODO - Pass worker ID or some other identifier,
// so that multiple envs won't overwrite each others stats.
TimerStack.Instance.SaveJsonTimers();
}
}
}