浏览代码

[WIP] Side Channel Design Changes (#3807)

* Make EnvironmentParameters a first-class citizen in the API

Missing: Python conterparts and testing.

* Minor comment fix to Engine Parameters

* A second minor fix.

* Make EngineConfigChannel Internal and add a singleton/sealed accessor

* Make StatsSideChannel Internal and add a singleton/sealed accessor

* Changes to SideChannelUtils

- Disallow two sidechannels of the same type to be added
- Remove GetSideChannels that return a list as that is now unnecessary
- Make most methods except (register/unregister) internal to limit users impacting the “system-level” side channels
- Add an improved comment to SideChannel.cs

* Added Dispose methods to system-level sidechannel wrappers

- Specifically to StatsRecorder, EnvironmentParameters and EngineParameters.
- Updated Academy.Dispose to take advantage of these.
- Updated Editor tests to cover all three “system-level” side channels.

Kudos to Unit Tests (TestAcade...
/develop/dockerfile
GitHub 5 年前
当前提交
ea0c6fa0
共有 49 个文件被更改,包括 915 次插入496 次删除
  1. 8
      Project/Assets/ML-Agents/Examples/3DBall/Scripts/Ball3DAgent.cs
  2. 8
      Project/Assets/ML-Agents/Examples/3DBall/Scripts/Ball3DHardAgent.cs
  3. 6
      Project/Assets/ML-Agents/Examples/Bouncer/Scripts/BouncerAgent.cs
  4. 7
      Project/Assets/ML-Agents/Examples/FoodCollector/Scripts/FoodCollectorAgent.cs
  5. 10
      Project/Assets/ML-Agents/Examples/FoodCollector/Scripts/FoodCollectorSettings.cs
  6. 9
      Project/Assets/ML-Agents/Examples/GridWorld/Scripts/GridAgent.cs
  7. 20
      Project/Assets/ML-Agents/Examples/GridWorld/Scripts/GridArea.cs
  8. 2
      Project/Assets/ML-Agents/Examples/GridWorld/Scripts/GridSettings.cs
  9. 18
      Project/Assets/ML-Agents/Examples/PushBlock/Scripts/PushAgentBasic.cs
  10. 13
      Project/Assets/ML-Agents/Examples/Reacher/Scripts/ReacherAgent.cs
  11. 3
      Project/Assets/ML-Agents/Examples/SharedAssets/Scripts/ProjectSettingsOverrides.cs
  12. 8
      Project/Assets/ML-Agents/Examples/Soccer/Scripts/AgentSoccer.cs
  13. 6
      Project/Assets/ML-Agents/Examples/Soccer/Scripts/SoccerFieldArea.cs
  14. 8
      Project/Assets/ML-Agents/Examples/Tennis/Scripts/TennisAgent.cs
  15. 10
      Project/Assets/ML-Agents/Examples/Walker/Scripts/WalkerAgent.cs
  16. 12
      Project/Assets/ML-Agents/Examples/WallJump/Scripts/WallJumpAgent.cs
  17. 23
      com.unity.ml-agents/CHANGELOG.md
  18. 42
      com.unity.ml-agents/Runtime/Academy.cs
  19. 4
      com.unity.ml-agents/Runtime/Communicator/RpcCommunicator.cs
  20. 58
      com.unity.ml-agents/Runtime/SideChannels/EngineConfigurationChannel.cs
  21. 30
      com.unity.ml-agents/Runtime/SideChannels/FloatPropertiesChannel.cs
  22. 2
      com.unity.ml-agents/Runtime/SideChannels/RawBytesChannel.cs
  23. 17
      com.unity.ml-agents/Runtime/SideChannels/SideChannel.cs
  24. 45
      com.unity.ml-agents/Runtime/SideChannels/StatsSideChannel.cs
  25. 2
      com.unity.ml-agents/Runtime/SideChannels/EnvironmentParametersChannel.cs.meta
  26. 16
      com.unity.ml-agents/Tests/Editor/MLAgentsEditModeTest.cs
  27. 32
      com.unity.ml-agents/Tests/Editor/SideChannelTests.cs
  28. 10
      docs/Custom-SideChannels.md
  29. 27
      docs/Migrating.md
  30. 36
      docs/Python-API.md
  31. 4
      docs/Training-Curriculum-Learning.md
  32. 4
      docs/Using-Tensorboard.md
  33. 2
      ml-agents-envs/mlagents_envs/environment.py
  34. 88
      ml-agents-envs/mlagents_envs/side_channel/engine_configuration_channel.py
  35. 5
      ml-agents/mlagents/trainers/env_manager.py
  36. 19
      ml-agents/mlagents/trainers/learn.py
  37. 14
      ml-agents/mlagents/trainers/simple_env_manager.py
  38. 19
      ml-agents/mlagents/trainers/subprocess_env_manager.py
  39. 6
      ml-agents/mlagents/trainers/tests/test_simple_rl.py
  40. 70
      com.unity.ml-agents/Runtime/EnvironmentParameters.cs
  41. 11
      com.unity.ml-agents/Runtime/EnvironmentParameters.cs.meta
  42. 91
      com.unity.ml-agents/Runtime/SideChannels/EnvironmentParametersChannel.cs
  43. 218
      com.unity.ml-agents/Runtime/SideChannels/SideChannelsManager.cs
  44. 11
      com.unity.ml-agents/Runtime/SideChannels/SideChannelsManager.cs.meta
  45. 71
      com.unity.ml-agents/Runtime/StatsRecorder.cs
  46. 11
      com.unity.ml-agents/Runtime/StatsRecorder.cs.meta
  47. 37
      ml-agents-envs/mlagents_envs/side_channel/environment_parameters_channel.py
  48. 238
      com.unity.ml-agents/Runtime/SideChannels/SideChannelUtils.cs
  49. 0
      /com.unity.ml-agents/Runtime/SideChannels/EnvironmentParametersChannel.cs.meta

8
Project/Assets/ML-Agents/Examples/3DBall/Scripts/Ball3DAgent.cs


[Header("Specific to Ball3D")]
public GameObject ball;
Rigidbody m_BallRb;
FloatPropertiesChannel m_ResetParams;
EnvironmentParameters m_ResetParams;
m_ResetParams = SideChannelUtils.GetSideChannel<FloatPropertiesChannel>();
m_ResetParams = Academy.Instance.EnvironmentParameters;
SetResetParameters();
}

public void SetBall()
{
//Set the attributes of the ball by fetching the information from the academy
m_BallRb.mass = m_ResetParams.GetPropertyWithDefault("mass", 1.0f);
var scale = m_ResetParams.GetPropertyWithDefault("scale", 1.0f);
m_BallRb.mass = m_ResetParams.GetWithDefault("mass", 1.0f);
var scale = m_ResetParams.GetWithDefault("scale", 1.0f);
ball.transform.localScale = new Vector3(scale, scale, scale);
}

8
Project/Assets/ML-Agents/Examples/3DBall/Scripts/Ball3DHardAgent.cs


[Header("Specific to Ball3DHard")]
public GameObject ball;
Rigidbody m_BallRb;
FloatPropertiesChannel m_ResetParams;
EnvironmentParameters m_ResetParams;
m_ResetParams = SideChannelUtils.GetSideChannel<FloatPropertiesChannel>();
m_ResetParams = Academy.Instance.EnvironmentParameters;
SetResetParameters();
}

public void SetBall()
{
//Set the attributes of the ball by fetching the information from the academy
m_BallRb.mass = m_ResetParams.GetPropertyWithDefault("mass", 1.0f);
var scale = m_ResetParams.GetPropertyWithDefault("scale", 1.0f);
m_BallRb.mass = m_ResetParams.GetWithDefault("mass", 1.0f);
var scale = m_ResetParams.GetWithDefault("scale", 1.0f);
ball.transform.localScale = new Vector3(scale, scale, scale);
}

6
Project/Assets/ML-Agents/Examples/Bouncer/Scripts/BouncerAgent.cs


int m_NumberJumps = 20;
int m_JumpLeft = 20;
FloatPropertiesChannel m_ResetParams;
EnvironmentParameters m_ResetParams;
public override void Initialize()
{

m_ResetParams = SideChannelUtils.GetSideChannel<FloatPropertiesChannel>();
m_ResetParams = Academy.Instance.EnvironmentParameters;
SetResetParameters();
}

public void SetTargetScale()
{
var targetScale = m_ResetParams.GetPropertyWithDefault("target_scale", 1.0f);
var targetScale = m_ResetParams.GetWithDefault("target_scale", 1.0f);
target.transform.localScale = new Vector3(targetScale, targetScale, targetScale);
}

7
Project/Assets/ML-Agents/Examples/FoodCollector/Scripts/FoodCollectorAgent.cs


public bool contribute;
public bool useVectorObs;
EnvironmentParameters m_ResetParams;
public override void Initialize()
{

m_ResetParams = Academy.Instance.EnvironmentParameters;
SetResetParameters();
}

public void SetLaserLengths()
{
m_LaserLength = SideChannelUtils.GetSideChannel<FloatPropertiesChannel>().GetPropertyWithDefault("laser_length", 1.0f);
m_LaserLength = m_ResetParams.GetWithDefault("laser_length", 1.0f);
float agentScale = SideChannelUtils.GetSideChannel<FloatPropertiesChannel>().GetPropertyWithDefault("agent_scale", 1.0f);
float agentScale = m_ResetParams.GetWithDefault("agent_scale", 1.0f);
gameObject.transform.localScale = new Vector3(agentScale, agentScale, agentScale);
}

10
Project/Assets/ML-Agents/Examples/FoodCollector/Scripts/FoodCollectorSettings.cs


using System;
using MLAgents.SideChannels;
public class FoodCollectorSettings : MonoBehaviour
{

public int totalScore;
public Text scoreText;
StatsSideChannel m_statsSideChannel;
StatsRecorder m_Recorder;
m_statsSideChannel = SideChannelUtils.GetSideChannel<StatsSideChannel>();
m_Recorder = Academy.Instance.StatsRecorder;
public void EnvironmentReset()
private void EnvironmentReset()
{
ClearObjects(GameObject.FindGameObjectsWithTag("food"));
ClearObjects(GameObject.FindGameObjectsWithTag("badFood"));

// need to send every Update() call.
if ((Time.frameCount % 100)== 0)
{
m_statsSideChannel?.AddStat("TotalScore", totalScore);
m_Recorder.Add("TotalScore", totalScore);
}
}
}

9
Project/Assets/ML-Agents/Examples/GridWorld/Scripts/GridAgent.cs


const int k_Left = 3;
const int k_Right = 4;
EnvironmentParameters m_ResetParams;
public override void Initialize()
{
m_ResetParams = Academy.Instance.EnvironmentParameters;
}
public override void CollectDiscreteActionMasks(DiscreteActionMasker actionMasker)
{
// Mask the necessary actions if selected by the user.

var positionX = (int)transform.position.x;
var positionZ = (int)transform.position.z;
var maxPosition = (int)SideChannelUtils.GetSideChannel<FloatPropertiesChannel>().GetPropertyWithDefault("gridSize", 5f) - 1;
var maxPosition = (int)m_ResetParams.GetWithDefault("gridSize", 5f) - 1;
if (positionX == 0)
{

20
Project/Assets/ML-Agents/Examples/GridWorld/Scripts/GridArea.cs


public GameObject trueAgent;
FloatPropertiesChannel m_ResetParameters;
Camera m_AgentCam;
public GameObject goalPref;

Vector3 m_InitialPosition;
EnvironmentParameters m_ResetParams;
m_ResetParameters = SideChannelUtils.GetSideChannel<FloatPropertiesChannel>();
m_ResetParams = Academy.Instance.EnvironmentParameters;
m_Objects = new[] { goalPref, pitPref };

m_InitialPosition = transform.position;
}
public void SetEnvironment()
private void SetEnvironment()
transform.position = m_InitialPosition * (m_ResetParameters.GetPropertyWithDefault("gridSize", 5f) + 1);
transform.position = m_InitialPosition * (m_ResetParams.GetWithDefault("gridSize", 5f) + 1);
for (var i = 0; i < (int)m_ResetParameters.GetPropertyWithDefault("numObstacles", 1); i++)
for (var i = 0; i < (int)m_ResetParams.GetWithDefault("numObstacles", 1); i++)
for (var i = 0; i < (int)m_ResetParameters.GetPropertyWithDefault("numGoals", 1f); i++)
for (var i = 0; i < (int)m_ResetParams.GetWithDefault("numGoals", 1f); i++)
var gridSize = (int)m_ResetParameters.GetPropertyWithDefault("gridSize", 5f);
var gridSize = (int)m_ResetParams.GetWithDefault("gridSize", 5f);
m_Plane.transform.localScale = new Vector3(gridSize / 10.0f, 1f, gridSize / 10.0f);
m_Plane.transform.localPosition = new Vector3((gridSize - 1) / 2f, -0.5f, (gridSize - 1) / 2f);
m_Sn.transform.localScale = new Vector3(1, 1, gridSize + 2);

public void AreaReset()
{
var gridSize = (int)m_ResetParameters.GetPropertyWithDefault("gridSize", 5f);
var gridSize = (int)m_ResetParams.GetWithDefault("gridSize", 5f);
foreach (var actor in actorObjs)
{
DestroyImmediate(actor);

{
numbers.Add(Random.Range(0, gridSize * gridSize));
}
var numbersA = Enumerable.ToArray(numbers);
var numbersA = numbers.ToArray();
for (var i = 0; i < players.Length; i++)
{

2
Project/Assets/ML-Agents/Examples/GridWorld/Scripts/GridSettings.cs


public void Awake()
{
SideChannelUtils.GetSideChannel<FloatPropertiesChannel>().RegisterCallback("gridSize", f =>
Academy.Instance.EnvironmentParameters.RegisterCallback("gridSize", f =>
{
MainCamera.transform.position = new Vector3(-(f - 1) / 2f, f * 1.25f, -(f - 1) / 2f);
MainCamera.orthographicSize = (f + 5f) / 2f;

18
Project/Assets/ML-Agents/Examples/PushBlock/Scripts/PushAgentBasic.cs


/// </summary>
Renderer m_GroundRenderer;
private EnvironmentParameters m_ResetParams;
void Awake()
{
m_PushBlockSettings = FindObjectOfType<PushBlockSettings>();

m_GroundRenderer = ground.GetComponent<Renderer>();
// Starting material
m_GroundMaterial = m_GroundRenderer.material;
m_ResetParams = Academy.Instance.EnvironmentParameters;
SetResetParameters();
}

public void SetGroundMaterialFriction()
{
var resetParams = SideChannelUtils.GetSideChannel<FloatPropertiesChannel>();
groundCollider.material.dynamicFriction = resetParams.GetPropertyWithDefault("dynamic_friction", 0);
groundCollider.material.staticFriction = resetParams.GetPropertyWithDefault("static_friction", 0);
groundCollider.material.dynamicFriction = m_ResetParams.GetWithDefault("dynamic_friction", 0);
groundCollider.material.staticFriction = m_ResetParams.GetWithDefault("static_friction", 0);
var resetParams = SideChannelUtils.GetSideChannel<FloatPropertiesChannel>();
var scale = resetParams.GetPropertyWithDefault("block_scale", 2);
var scale = m_ResetParams.GetWithDefault("block_scale", 2);
m_BlockRb.drag = resetParams.GetPropertyWithDefault("block_drag", 0.5f);
m_BlockRb.drag = m_ResetParams.GetWithDefault("block_drag", 0.5f);
public void SetResetParameters()
private void SetResetParameters()
{
SetGroundMaterialFriction();
SetBlockProperties();

13
Project/Assets/ML-Agents/Examples/Reacher/Scripts/ReacherAgent.cs


// Frequency of the cosine deviation of the goal along the vertical dimension
float m_DeviationFreq;
private EnvironmentParameters m_ResetParams;
/// <summary>
/// Collect the rigidbodies of the reacher in order to resue them for
/// observations and actions.

m_RbA = pendulumA.GetComponent<Rigidbody>();
m_RbB = pendulumB.GetComponent<Rigidbody>();
m_ResetParams = Academy.Instance.EnvironmentParameters;
SetResetParameters();
}

public void SetResetParameters()
{
var fp = SideChannelUtils.GetSideChannel<FloatPropertiesChannel>();
m_GoalSize = fp.GetPropertyWithDefault("goal_size", 5);
m_GoalSpeed = Random.Range(-1f, 1f) * fp.GetPropertyWithDefault("goal_speed", 1);
m_Deviation = fp.GetPropertyWithDefault("deviation", 0);
m_DeviationFreq = fp.GetPropertyWithDefault("deviation_freq", 0);
m_GoalSize = m_ResetParams.GetWithDefault("goal_size", 5);
m_GoalSpeed = Random.Range(-1f, 1f) * m_ResetParams.GetWithDefault("goal_speed", 1);
m_Deviation = m_ResetParams.GetWithDefault("deviation", 0);
m_DeviationFreq = m_ResetParams.GetWithDefault("deviation_freq", 0);
}
}

3
Project/Assets/ML-Agents/Examples/SharedAssets/Scripts/ProjectSettingsOverrides.cs


Physics.defaultSolverVelocityIterations = solverVelocityIterations;
// Make sure the Academy singleton is initialized first, since it will create the SideChannels.
var academy = Academy.Instance;
SideChannelUtils.GetSideChannel<FloatPropertiesChannel>().RegisterCallback("gravity", f => { Physics.gravity = new Vector3(0, -f, 0); });
Academy.Instance.EnvironmentParameters.RegisterCallback("gravity", f => { Physics.gravity = new Vector3(0, -f, 0); });
}
public void OnDestroy()

8
Project/Assets/ML-Agents/Examples/Soccer/Scripts/AgentSoccer.cs


BehaviorParameters m_BehaviorParameters;
Vector3 m_Transform;
private EnvironmentParameters m_ResetParams;
public override void Initialize()
{
m_Existential = 1f / MaxStep;

m_LateralSpeed = 0.3f;
m_ForwardSpeed = 1.3f;
}
else
else
{
m_LateralSpeed = 0.3f;
m_ForwardSpeed = 1.0f;

area.playerStates.Add(playerState);
m_PlayerIndex = area.playerStates.IndexOf(playerState);
playerState.playerIndex = m_PlayerIndex;
m_ResetParams = Academy.Instance.EnvironmentParameters;
}
public void MoveAgent(float[] act)

{
timePenalty = 0;
m_BallTouch = SideChannelUtils.GetSideChannel<FloatPropertiesChannel>().GetPropertyWithDefault("ball_touch", 0);
m_BallTouch = m_ResetParams.GetWithDefault("ball_touch", 0);
if (team == Team.Purple)
{
transform.rotation = Quaternion.Euler(0f, -90f, 0f);

6
Project/Assets/ML-Agents/Examples/Soccer/Scripts/SoccerFieldArea.cs


[HideInInspector]
public bool canResetBall;
private EnvironmentParameters m_ResetParams;
void Awake()
{
canResetBall = true;

m_BallController.area = this;
ballStartingPos = ball.transform.position;
m_ResetParams = Academy.Instance.EnvironmentParameters;
}
IEnumerator ShowGoalUI()

ballRb.velocity = Vector3.zero;
ballRb.angularVelocity = Vector3.zero;
var ballScale = SideChannelUtils.GetSideChannel<FloatPropertiesChannel>().GetPropertyWithDefault("ball_scale", 0.015f);
var ballScale = m_ResetParams.GetWithDefault("ball_scale", 0.015f);
ballRb.transform.localScale = new Vector3(ballScale, ballScale, ballScale);
}
}

8
Project/Assets/ML-Agents/Examples/Tennis/Scripts/TennisAgent.cs


Rigidbody m_AgentRb;
Rigidbody m_BallRb;
float m_InvertMult;
FloatPropertiesChannel m_ResetParams;
EnvironmentParameters m_ResetParams;
// Looks for the scoreboard based on the name of the gameObjects.
// Do not modify the names of the Score GameObjects

m_BallRb = ball.GetComponent<Rigidbody>();
var canvas = GameObject.Find(k_CanvasName);
GameObject scoreBoard;
m_ResetParams = SideChannelUtils.GetSideChannel<FloatPropertiesChannel>();
m_ResetParams = Academy.Instance.EnvironmentParameters;
if (invertX)
{
scoreBoard = canvas.transform.Find(k_ScoreBoardBName).gameObject;

public void SetRacket()
{
angle = m_ResetParams.GetPropertyWithDefault("angle", 55);
angle = m_ResetParams.GetWithDefault("angle", 55);
gameObject.transform.eulerAngles = new Vector3(
gameObject.transform.eulerAngles.x,
gameObject.transform.eulerAngles.y,

public void SetBall()
{
scale = m_ResetParams.GetPropertyWithDefault("scale", .5f);
scale = m_ResetParams.GetWithDefault("scale", .5f);
ball.transform.localScale = new Vector3(scale, scale, scale);
}

10
Project/Assets/ML-Agents/Examples/Walker/Scripts/WalkerAgent.cs


Rigidbody m_ChestRb;
Rigidbody m_SpineRb;
FloatPropertiesChannel m_ResetParams;
EnvironmentParameters m_ResetParams;
public override void Initialize()
{

m_ChestRb = chest.GetComponent<Rigidbody>();
m_SpineRb = spine.GetComponent<Rigidbody>();
m_ResetParams = SideChannelUtils.GetSideChannel<FloatPropertiesChannel>();
m_ResetParams = Academy.Instance.EnvironmentParameters;
SetResetParameters();
}

public void SetTorsoMass()
{
m_ChestRb.mass = m_ResetParams.GetPropertyWithDefault("chest_mass", 8);
m_SpineRb.mass = m_ResetParams.GetPropertyWithDefault("spine_mass", 10);
m_HipsRb.mass = m_ResetParams.GetPropertyWithDefault("hip_mass", 15);
m_ChestRb.mass = m_ResetParams.GetWithDefault("chest_mass", 8);
m_SpineRb.mass = m_ResetParams.GetWithDefault("spine_mass", 10);
m_HipsRb.mass = m_ResetParams.GetWithDefault("hip_mass", 15);
}
public void SetResetParameters()

12
Project/Assets/ML-Agents/Examples/WallJump/Scripts/WallJumpAgent.cs


Vector3 m_JumpTargetPos;
Vector3 m_JumpStartingPos;
FloatPropertiesChannel m_FloatProperties;
EnvironmentParameters m_ResetParams;
public override void Initialize()
{

spawnArea.SetActive(false);
m_FloatProperties = SideChannelUtils.GetSideChannel<FloatPropertiesChannel>();
m_ResetParams = Academy.Instance.EnvironmentParameters;
}
// Begin the jump sequence

{
localScale = new Vector3(
localScale.x,
m_FloatProperties.GetPropertyWithDefault("no_wall_height", 0),
m_ResetParams.GetWithDefault("no_wall_height", 0),
localScale.z);
wall.transform.localScale = localScale;
SetModel("SmallWallJump", noWallBrain);

localScale = new Vector3(
localScale.x,
m_FloatProperties.GetPropertyWithDefault("small_wall_height", 4),
m_ResetParams.GetWithDefault("small_wall_height", 4),
localScale.z);
wall.transform.localScale = localScale;
SetModel("SmallWallJump", smallWallBrain);

var min = m_FloatProperties.GetPropertyWithDefault("big_wall_min_height", 8);
var max = m_FloatProperties.GetPropertyWithDefault("big_wall_max_height", 8);
var min = m_ResetParams.GetWithDefault("big_wall_min_height", 8);
var max = m_ResetParams.GetWithDefault("big_wall_max_height", 8);
var height = min + Random.value * (max - min);
localScale = new Vector3(
localScale.x,

23
com.unity.ml-agents/CHANGELOG.md


- The `--load` and `--train` command-line flags have been deprecated. Training
now happens by default, and use `--resume` to resume training instead. (#3705)
- The Jupyter notebooks have been removed from the repository.
- Introduced the `SideChannelUtils` to register, unregister and access side
channels.
- `Academy.FloatProperties` was removed, please use
`SideChannelUtils.GetSideChannel<FloatPropertiesChannel>()` instead.
- Removed the multi-agent gym option from the gym wrapper. For multi-agent
scenarios, use the [Low Level Python API](../docs/Python-API.md).
- The low level Python API has changed. You can look at the document

`AgentAction` and `AgentReset` have been removed.
- The GhostTrainer has been extended to support asymmetric games and the
asymmetric example environment Strikers Vs. Goalie has been added.
- The SideChannel API has changed (#3833, #3660) :
- Introduced the `SideChannelManager` to register, unregister and access side
channels.
- `EnvironmentParameters` replaces the default `FloatProperties`.
You can access the `EnvironmentParameters` with
`Academy.Instance.EnvironmentParameters` on C# and create an
`EnvironmentParametersChannel` on Python
- `SideChannel.OnMessageReceived` is now a protected method (was public)
- SideChannel IncomingMessages methods now take an optional default argument,
which is used when trying to read more data than the message contains.
- Added a feature to allow sending stats from C# environments to TensorBoard
(and other python StatsWriters). To do this from your code, use
`Academy.Instance.StatsRecorder.Add(key, value)`(#3660)
- CameraSensorComponent.m_Grayscale and RenderTextureSensorComponent.m_Grayscale
were changed from `public` to `private` (#3808).
- The `UnityEnv` class from the `gym-unity` package was renamed

- Format of console output has changed slightly and now matches the name of the
model/summary directory. (#3630, #3616)
- Added a feature to allow sending stats from C# environments to TensorBoard
(and other python StatsWriters). To do this from your code, use
`SideChannelUtils.GetSideChannel<StatsSideChannel>().AddStat(key, value)`
(#3660)
- SideChannel IncomingMessages methods now take an optional default argument,
which is used when trying to read more data than the message contains.
- The way that UnityEnvironment decides the port was changed. If no port is
specified, the behavior will depend on the `file_name` parameter. If it is
`None`, 5004 (the editor port) will be used; otherwise 5005 (the base

42
com.unity.ml-agents/Runtime/Academy.cs


/// Access the Academy singleton through the <see cref="Instance"/>
/// property. The Academy instance is initialized the first time it is accessed (which will
/// typically be by the first <see cref="Agent"/> initialized in a scene).
///
///
/// At initialization, the Academy attempts to connect to the Python training process through
/// the external communicator. If successful, the training process can train <see cref="Agent"/>
/// instances. When you set an agent's <see cref="BehaviorParameters.behaviorType"/> setting

/// on each side, although we may allow some flexibility in the future.
/// This should be incremented whenever a change is made to the communication protocol.
/// </summary>
const string k_ApiVersion = "0.16.0";
const string k_ApiVersion = "0.17.0";
/// <summary>
/// Unity package version of com.unity.ml-agents.

}
}
private EnvironmentParameters m_EnvironmentParameters;
private StatsRecorder m_StatsRecorder;
/// <summary>
/// Returns the <see cref="EnvironmentParameters"/> instance. If training
/// features such as Curriculum Learning or Environment Parameter Randomization are used,
/// then the values of the parameters generated from the training process can be
/// retrieved here.
/// </summary>
/// <returns></returns>
public EnvironmentParameters EnvironmentParameters
{
get { return m_EnvironmentParameters; }
}
/// <summary>
/// Returns the <see cref="StatsRecorder"/> instance. This instance can be used
/// to record any statistics from the Unity environment.
/// </summary>
/// <returns></returns>
public StatsRecorder StatsRecorder
{
get { return m_StatsRecorder; }
}
/// <summary>
/// Initializes the environment, configures it and initializes the Academy.
/// </summary>

EnableAutomaticStepping();
SideChannelUtils.RegisterSideChannel(new EngineConfigurationChannel());
SideChannelUtils.RegisterSideChannel(new FloatPropertiesChannel());
SideChannelUtils.RegisterSideChannel(new StatsSideChannel());
SideChannelsManager.RegisterSideChannel(new EngineConfigurationChannel());
m_EnvironmentParameters = new EnvironmentParameters();
m_StatsRecorder = new StatsRecorder();
// Try to launch the communicator by using the arguments passed at launch
var port = ReadPortFromArgs();

// If the communicator is not on, we need to clear the SideChannel sending queue
if (!IsCommunicatorOn)
{
SideChannelUtils.GetSideChannelMessage();
SideChannelsManager.GetSideChannelMessage();
}
using (TimerStack.Instance.Scoped("AgentAct"))

Communicator?.Dispose();
Communicator = null;
SideChannelUtils.UnregisterAllSideChannels();
m_EnvironmentParameters.Dispose();
m_StatsRecorder.Dispose();
SideChannelsManager.UnregisterAllSideChannels(); // unregister custom side channels
if (m_ModelRunners != null)
{

4
com.unity.ml-agents/Runtime/Communicator/RpcCommunicator.cs


void UpdateEnvironmentWithInput(UnityRLInputProto rlInput)
{
SideChannelUtils.ProcessSideChannelData(rlInput.SideChannel.ToArray());
SideChannelsManager.ProcessSideChannelData(rlInput.SideChannel.ToArray());
SendCommandEvent(rlInput.Command);
}

message.RlInitializationOutput = tempUnityRlInitializationOutput;
}
byte[] messageAggregated = SideChannelUtils.GetSideChannelMessage();
byte[] messageAggregated = SideChannelsManager.GetSideChannelMessage();
message.RlOutput.SideChannel = ByteString.CopyFrom(messageAggregated);
var input = Exchange(message);

58
com.unity.ml-agents/Runtime/SideChannels/EngineConfigurationChannel.cs


namespace MLAgents.SideChannels
{
public class EngineConfigurationChannel : SideChannel
internal class EngineConfigurationChannel : SideChannel
private enum ConfigurationType : int
{
ScreenResolution = 0,
QualityLevel = 1,
TimeScale = 2,
TargetFrameRate = 3,
CaptureFrameRate = 4
}
const string k_EngineConfigId = "e951342c-4f7e-11ea-b238-784f4387d1f7";
/// <summary>

}
/// <inheritdoc/>
public override void OnMessageReceived(IncomingMessage msg)
protected override void OnMessageReceived(IncomingMessage msg)
var width = msg.ReadInt32();
var height = msg.ReadInt32();
var qualityLevel = msg.ReadInt32();
var timeScale = msg.ReadFloat32();
var targetFrameRate = msg.ReadInt32();
timeScale = Mathf.Clamp(timeScale, 1, 100);
Screen.SetResolution(width, height, false);
QualitySettings.SetQualityLevel(qualityLevel, true);
Time.timeScale = timeScale;
Time.captureFramerate = 60;
Application.targetFrameRate = targetFrameRate;
var messageType = (ConfigurationType)msg.ReadInt32();
switch (messageType)
{
case ConfigurationType.ScreenResolution:
var width = msg.ReadInt32();
var height = msg.ReadInt32();
Screen.SetResolution(width, height, false);
break;
case ConfigurationType.QualityLevel:
var qualityLevel = msg.ReadInt32();
QualitySettings.SetQualityLevel(qualityLevel, true);
break;
case ConfigurationType.TimeScale:
var timeScale = msg.ReadFloat32();
timeScale = Mathf.Clamp(timeScale, 1, 100);
Time.timeScale = timeScale;
break;
case ConfigurationType.TargetFrameRate:
var targetFrameRate = msg.ReadInt32();
Application.targetFrameRate = targetFrameRate;
break;
case ConfigurationType.CaptureFrameRate:
var captureFrameRate = msg.ReadInt32();
Time.captureFramerate = captureFrameRate;
break;
default:
Debug.LogWarning(
"Unknown engine configuration received from Python. Make sure" +
" your Unity and Python versions are compatible.");
break;
}
}
}
}

30
com.unity.ml-agents/Runtime/SideChannels/FloatPropertiesChannel.cs


}
/// <inheritdoc/>
public override void OnMessageReceived(IncomingMessage msg)
protected override void OnMessageReceived(IncomingMessage msg)
{
var key = msg.ReadString();
var value = msg.ReadFloat32();

action?.Invoke(value);
}
/// <inheritdoc/>
public void SetProperty(string key, float value)
/// <summary>
/// Sets one of the float properties of the environment. This data will be sent to Python.
/// </summary>
/// <param name="key"> The string identifier of the property.</param>
/// <param name="value"> The float value of the property.</param>
public void Set(string key, float value)
{
m_FloatProperties[key] = value;
using (var msgOut = new OutgoingMessage())

action?.Invoke(value);
}
public float GetPropertyWithDefault(string key, float defaultValue)
/// <summary>
/// Get an Environment property with a default value. If there is a value for this property,
/// it will be returned, otherwise, the default value will be returned.
/// </summary>
/// <param name="key"> The string identifier of the property.</param>
/// <param name="defaultValue"> The default value of the property.</param>
/// <returns></returns>
public float GetWithDefault(string key, float defaultValue)
{
float valueOut;
bool hasKey = m_FloatProperties.TryGetValue(key, out valueOut);

/// <summary>
/// Registers an action to be performed everytime the property is changed.
/// </summary>
/// <param name="key"> The string identifier of the property.</param>
/// <param name="action"> The action that ill be performed. Takes a float as input.</param>
public IList<string> ListProperties()
/// <summary>
/// Returns a list of all the string identifiers of the properties currently present.
/// </summary>
/// <returns> The list of string identifiers </returns>
public IList<string> Keys()
{
return new List<string>(m_FloatProperties.Keys);
}

2
com.unity.ml-agents/Runtime/SideChannels/RawBytesChannel.cs


}
/// <inheritdoc/>
public override void OnMessageReceived(IncomingMessage msg)
protected override void OnMessageReceived(IncomingMessage msg)
{
m_MessagesReceived.Add(msg.GetRawBytes());
}

17
com.unity.ml-agents/Runtime/SideChannels/SideChannel.cs


/// Side channels provide an alternative mechanism of sending/receiving data from Unity
/// to Python that is outside of the traditional machine learning loop. ML-Agents provides
/// some specific implementations of side channels, but users can create their own.
///
/// To create your own, you'll need to create two, new mirrored classes, one in Unity (by
/// extending <see cref="SideChannel"/>) and another in Python by extending a Python class
/// also called SideChannel. Then, within your project, use
/// <see cref="SideChannelsManager.RegisterSideChannel"/> and
/// <see cref="SideChannelsManager.UnregisterSideChannel"/> to register and unregister your
/// custom side channel.
/// </summary>
public abstract class SideChannel
{

protected set;
}
internal void ProcessMessage(byte[] msg)
{
using (var incomingMsg = new IncomingMessage(msg))
{
OnMessageReceived(incomingMsg);
}
}
public abstract void OnMessageReceived(IncomingMessage msg);
protected abstract void OnMessageReceived(IncomingMessage msg);
/// <summary>
/// Queues a message to be sent to Python during the next simulation step.

45
com.unity.ml-agents/Runtime/SideChannels/StatsSideChannel.cs


namespace MLAgents.SideChannels
{
/// <summary>
/// Determines the behavior of how multiple stats within the same summary period are combined.
/// A Side Channel for sending <see cref="StatsRecorder"/> data.
public enum StatAggregationMethod
{
/// <summary>
/// Values within the summary period are averaged before reporting.
/// Note that values from the same C# environment in the same step may replace each other.
/// </summary>
Average = 0,
/// <summary>
/// Only the most recent value is reported.
/// To avoid conflicts between multiple environments, the ML Agents environment will only
/// keep stats from worker index 0.
/// </summary>
MostRecent = 1
}
/// <summary>
/// Add stats (key-value pairs) for reporting. The ML Agents environment will send these to a StatsReporter
/// instance, which means the values will appear in the Tensorboard summary, as well as trainer gauges.
/// Note that stats are only written every summary_frequency steps; See <see cref="StatAggregationMethod"/>
/// for options on how multiple values are handled.
/// </summary>
public class StatsSideChannel : SideChannel
internal class StatsSideChannel : SideChannel
/// Initializes the side channel with the provided channel ID.
/// The constructor is internal because only one instance is
/// supported at a time, and is created by the Academy.
/// Initializes the side channel. The constructor is internal because only one instance is
/// supported at a time.
/// </summary>
internal StatsSideChannel()
{

/// <summary>
/// Add a stat value for reporting. This will appear in the Tensorboard summary and trainer gauges.
/// You can nest stats in Tensorboard with "/".
/// Note that stats are only written to Tensorboard each summary_frequency steps; if a stat is
/// received multiple times, only the most recent version is used.
/// To avoid conflicts between multiple environments, only stats from worker index 0 are used.
/// Add a stat value for reporting.
/// <param name="value">The stat value. You can nest stats in Tensorboard by using "/". </param>
/// <param name="value">The stat value.</param>
public void AddStat(
string key, float value, StatAggregationMethod aggregationMethod = StatAggregationMethod.Average
)
public void AddStat(string key, float value, StatAggregationMethod aggregationMethod)
{
using (var msg = new OutgoingMessage())
{

}
/// <inheritdoc/>
public override void OnMessageReceived(IncomingMessage msg)
protected override void OnMessageReceived(IncomingMessage msg)
{
throw new UnityAgentsException("StatsSideChannel should never receive messages.");
}

2
com.unity.ml-agents/Runtime/SideChannels/EnvironmentParametersChannel.cs.meta


fileFormatVersion: 2
guid: 2506dff31271f49298fbff21e13fa8b6
guid: a849760d5bec946b884984e35c66fcfa
MonoImporter:
externalObjects: {}
serializedVersion: 2

16
com.unity.ml-agents/Tests/Editor/MLAgentsEditModeTest.cs


Assert.AreEqual(0, aca.EpisodeCount);
Assert.AreEqual(0, aca.StepCount);
Assert.AreEqual(0, aca.TotalStepCount);
Assert.AreNotEqual(null, SideChannelUtils.GetSideChannel<FloatPropertiesChannel>());
Assert.AreNotEqual(null, SideChannelsManager.GetSideChannel<EnvironmentParametersChannel>());
Assert.AreNotEqual(null, SideChannelsManager.GetSideChannel<EngineConfigurationChannel>());
Assert.AreNotEqual(null, SideChannelsManager.GetSideChannel<StatsSideChannel>());
// Check that Dispose is idempotent
aca.Dispose();

[Test]
public void TestAcademyDispose()
{
var floatProperties1 = SideChannelUtils.GetSideChannel<FloatPropertiesChannel>();
var envParams1 = SideChannelsManager.GetSideChannel<EnvironmentParametersChannel>();
var engineParams1 = SideChannelsManager.GetSideChannel<EngineConfigurationChannel>();
var statsParams1 = SideChannelsManager.GetSideChannel<StatsSideChannel>();
var floatProperties2 = SideChannelUtils.GetSideChannel<FloatPropertiesChannel>();
var envParams2 = SideChannelsManager.GetSideChannel<EnvironmentParametersChannel>();
var engineParams2 = SideChannelsManager.GetSideChannel<EngineConfigurationChannel>();
var statsParams2 = SideChannelsManager.GetSideChannel<StatsSideChannel>();
Assert.AreNotEqual(floatProperties1, floatProperties2);
Assert.AreNotEqual(envParams1, envParams2);
Assert.AreNotEqual(engineParams1, engineParams2);
Assert.AreNotEqual(statsParams1, statsParams2);
}
[Test]

32
com.unity.ml-agents/Tests/Editor/SideChannelTests.cs


ChannelId = new Guid("6afa2c06-4f82-11ea-b238-784f4387d1f7");
}
public override void OnMessageReceived(IncomingMessage msg)
protected override void OnMessageReceived(IncomingMessage msg)
{
messagesReceived.Add(msg.ReadInt32());
}

intSender.SendInt(5);
intSender.SendInt(6);
byte[] fakeData = SideChannelUtils.GetSideChannelMessage(dictSender);
SideChannelUtils.ProcessSideChannelData(dictReceiver, fakeData);
byte[] fakeData = SideChannelsManager.GetSideChannelMessage(dictSender);
SideChannelsManager.ProcessSideChannelData(dictReceiver, fakeData);
Assert.AreEqual(intReceiver.messagesReceived[0], 4);
Assert.AreEqual(intReceiver.messagesReceived[1], 5);

strSender.SendRawBytes(Encoding.ASCII.GetBytes(str1));
strSender.SendRawBytes(Encoding.ASCII.GetBytes(str2));
byte[] fakeData = SideChannelUtils.GetSideChannelMessage(dictSender);
SideChannelUtils.ProcessSideChannelData(dictReceiver, fakeData);
byte[] fakeData = SideChannelsManager.GetSideChannelMessage(dictSender);
SideChannelsManager.ProcessSideChannelData(dictReceiver, fakeData);
var messages = strReceiver.GetAndClearReceivedMessages();

var dictSender = new Dictionary<Guid, SideChannel> { { propB.ChannelId, propB } };
propA.RegisterCallback(k1, f => { wasCalled++; });
var tmp = propB.GetPropertyWithDefault(k2, 3.0f);
var tmp = propB.GetWithDefault(k2, 3.0f);
propB.SetProperty(k2, 1.0f);
tmp = propB.GetPropertyWithDefault(k2, 3.0f);
propB.Set(k2, 1.0f);
tmp = propB.GetWithDefault(k2, 3.0f);
byte[] fakeData = SideChannelUtils.GetSideChannelMessage(dictSender);
SideChannelUtils.ProcessSideChannelData(dictReceiver, fakeData);
byte[] fakeData = SideChannelsManager.GetSideChannelMessage(dictSender);
SideChannelsManager.ProcessSideChannelData(dictReceiver, fakeData);
tmp = propA.GetPropertyWithDefault(k2, 3.0f);
tmp = propA.GetWithDefault(k2, 3.0f);
propB.SetProperty(k1, 1.0f);
propB.Set(k1, 1.0f);
fakeData = SideChannelUtils.GetSideChannelMessage(dictSender);
SideChannelUtils.ProcessSideChannelData(dictReceiver, fakeData);
fakeData = SideChannelsManager.GetSideChannelMessage(dictSender);
SideChannelsManager.ProcessSideChannelData(dictReceiver, fakeData);
var keysA = propA.ListProperties();
var keysA = propA.Keys();
var keysB = propA.ListProperties();
var keysB = propA.Keys();
Assert.AreEqual(2, keysB.Count);
Assert.IsTrue(keysB.Contains(k1));
Assert.IsTrue(keysB.Contains(k2));

10
docs/Custom-SideChannels.md


You can create your own side channel in C# and Python and use it to communicate
custom data structures between the two. This can be useful for situations in
which the data to be sent is too complex or structured for the built-in
`FloatPropertiesChannel`, or is not related to any specific agent, and therefore
`EnvironmentParameters`, or is not related to any specific agent, and therefore
inappropriate as an agent observation.
## Overview

`base.QueueMessageToSend(msg)` method inside the side channel, and call the
`OutgoingMessage.Dispose()` method.
To register a side channel on the Unity side, call `SideChannelUtils.RegisterSideChannel` with the side channel
To register a side channel on the Unity side, call `SideChannelManager.RegisterSideChannel` with the side channel
as only argument.
### Python side

// When a Debug.Log message is created, we send it to the stringChannel
Application.logMessageReceived += stringChannel.SendDebugStatementToPython;
// The channel must be registered with the SideChannelUtils class
SideChannelUtils.RegisterSideChannel(stringChannel);
// The channel must be registered with the SideChannelManager class
SideChannelManager.RegisterSideChannel(stringChannel);
}
public void OnDestroy()

if (Academy.IsInitialized){
SideChannelUtils.UnregisterSideChannel(stringChannel);
SideChannelManager.UnregisterSideChannel(stringChannel);
}
}

27
docs/Migrating.md


- The signature of `Agent.Heuristic()` was changed to take a `float[]` as a
parameter, instead of returning the array. This was done to prevent a common
source of error where users would return arrays of the wrong size.
- The SideChannel API has changed (#3833, #3660) :
- Introduced the `SideChannelManager` to register, unregister and access side
channels.
- `EnvironmentParameters` replaces the default `FloatProperties`.
You can access the `EnvironmentParameters` with
`Academy.Instance.EnvironmentParameters` on C# and create an
`EnvironmentParametersChannel` on Python
- `SideChannel.OnMessageReceived` is now a protected method (was public)
- SideChannel IncomingMessages methods now take an optional default argument,
which is used when trying to read more data than the message contains.
- Added a feature to allow sending stats from C# environments to TensorBoard
(and other python StatsWriters). To do this from your code, use
`Academy.Instance.StatsRecorder.Add(key, value)`(#3660)
- `num_updates` and `train_interval` for SAC have been replaced with `steps_per_update`.
- The `UnityEnv` class from the `gym-unity` package was renamed
`UnityToGymWrapper` and no longer creates the `UnityEnvironment`. Instead,

- To force-overwrite files from a pre-existing run, add the `--force`
command-line flag.
- The Jupyter notebooks have been removed from the repository.
- `Academy.FloatProperties` was removed.
- `Academy.RegisterSideChannel` and `Academy.UnregisterSideChannel` were
removed.
- Replace `Academy.FloatProperties` with
`SideChannelUtils.GetSideChannel<FloatPropertiesChannel>()`.
- Replace `Academy.RegisterSideChannel` with
`SideChannelUtils.RegisterSideChannel()`.
- Replace `Academy.UnregisterSideChannel` with
`SideChannelUtils.UnregisterSideChannel`.
- If you used `SideChannels` you must:
- Replace `Academy.FloatProperties` with `Academy.Instance.EnvironmentParameters`.
- `Academy.RegisterSideChannel` and `Academy.UnregisterSideChannel` were
removed. Use `SideChannelManager.RegisterSideChannel` and
`SideChannelManager.UnregisterSideChannel` instead.
- Set `steps_per_update` to be around equal to the number of agents in your environment,
times `num_updates` and divided by `train_interval`.
- Replace `UnityEnv` with `UnityToGymWrapper` in your code. The constructor

36
docs/Python-API.md


`EngineConfigurationChannel` has two methods :
* `set_configuration_parameters` which takes the following arguments:
* `width`: Defines the width of the display. Default 80.
* `height`: Defines the height of the display. Default 80.
* `quality_level`: Defines the quality level of the simulation. Default 1.
* `time_scale`: Defines the multiplier for the deltatime in the simulation. If set to a higher value, time will pass faster in the simulation but the physics may perform unpredictably. Default 20.
* `target_frame_rate`: Instructs simulation to try to render at a specified frame rate. Default -1.
* `width`: Defines the width of the display. (Must be set alongside height)
* `height`: Defines the height of the display. (Must be set alongside width)
* `quality_level`: Defines the quality level of the simulation.
* `time_scale`: Defines the multiplier for the deltatime in the simulation. If set to a higher value, time will pass faster in the simulation but the physics may perform unpredictably.
* `target_frame_rate`: Instructs simulation to try to render at a specified frame rate.
* `capture_frame_rate` Instructs the simulation to consider time between updates to always be constant, regardless of the actual frame rate.
* `set_configuration` with argument config which is an `EngineConfig`
NamedTuple object.

...
```
#### FloatPropertiesChannel
The `FloatPropertiesChannel` will allow you to get and set pre-defined numerical values in the environment. This can be useful for adjusting environment-specific settings, or for reading non-agent related information from the environment. You can call `get_property` and `set_property` on the side channel to read and write properties.
#### EnvironmentParameters
The `EnvironmentParameters` will allow you to get and set pre-defined numerical values in the environment. This can be useful for adjusting environment-specific settings, or for reading non-agent related information from the environment. You can call `get_property` and `set_property` on the side channel to read and write properties.
`FloatPropertiesChannel` has three methods:
`EnvironmentParametersChannel` has one methods:
* `set_property` Sets a property in the Unity Environment.
* `set_float_parameter` Sets a float parameter in the Unity Environment.
* `get_property` Gets a property in the Unity Environment. If the property was not found, will return None.
* key: The string identifier of the property.
* `list_properties` Returns a list of all the string identifiers of the properties
from mlagents_envs.side_channel.float_properties_channel import FloatPropertiesChannel
from mlagents_envs.side_channel.environment_parameters_channel import EnvironmentParametersChannel
channel = FloatPropertiesChannel()
channel = EnvironmentParametersChannel()
channel.set_property("parameter_1", 2.0)
channel.set_float_parameter("parameter_1", 2.0)
readout_value = channel.get_property("parameter_2")
...
```

var sharedProperties = SideChannelUtils.GetSideChannel<FloatPropertiesChannel>();
float property1 = sharedProperties.GetPropertyWithDefault("parameter_1", 0.0f);
var envParameters = Academy.Instance.EnvironmentParameters;
float property1 = envParameters.GetWithDefault("parameter_1", 0.0f);
```
#### Custom side channels

4
docs/Training-Curriculum-Learning.md


In order to define the curricula, the first step is to decide which parameters of
the environment will vary. In the case of the Wall Jump environment,
the height of the wall is what varies. We define this as a `Shared Float Property`
that can be accessed in `SideChannelUtils.GetSideChannel<FloatPropertiesChannel>()`, and by doing
the height of the wall is what varies. We define this as a `Environment Parameters`
that can be accessed in `Academy.Instance.EnvironmentParameters`, and by doing
so it becomes adjustable via the Python API.
Rather than adjusting it by hand, we will create a YAML file which
describes the structure of the curricula. Within it, we can specify which

4
docs/Using-Tensorboard.md


StatsSideChannel:
```csharp
var statsSideChannel = SideChannelUtils.GetSideChannel<StatsSideChannel>();
statsSideChannel.AddStat("MyMetric", 1.0);
var statsRecorder = Academy.Instance.StatsRecorder;
statsSideChannel.Add("MyMetric", 1.0);
```

2
ml-agents-envs/mlagents_envs/environment.py


# Currently we require strict equality between the communication protocol
# on each side, although we may allow some flexibility in the future.
# This should be incremented whenever a change is made to the communication protocol.
API_VERSION = "0.16.0"
API_VERSION = "0.17.0"
# Default port that the editor listens on. If an environment executable
# isn't specified, this port will be used.

88
ml-agents-envs/mlagents_envs/side_channel/engine_configuration_channel.py


from mlagents_envs.side_channel import SideChannel, OutgoingMessage, IncomingMessage
from mlagents_envs.exception import UnityCommunicationException
from mlagents_envs.exception import (
UnityCommunicationException,
UnitySideChannelException,
)
from typing import NamedTuple
from typing import NamedTuple, Optional
from enum import IntEnum
class EngineConfig(NamedTuple):

time_scale: float
target_frame_rate: int
capture_frame_rate: int
return EngineConfig(80, 80, 1, 20.0, -1)
return EngineConfig(80, 80, 1, 20.0, -1, 60)
class EngineConfigurationChannel(SideChannel):

- int qualityLevel;
- float timeScale;
- int targetFrameRate;
- int captureFrameRate;
class ConfigurationType(IntEnum):
SCREEN_RESOLUTION = 0
QUALITY_LEVEL = 1
TIME_SCALE = 2
TARGET_FRAME_RATE = 3
CAPTURE_FRAME_RATE = 4
def __init__(self) -> None:
super().__init__(uuid.UUID("e951342c-4f7e-11ea-b238-784f4387d1f7"))

def set_configuration_parameters(
self,
width: int = 80,
height: int = 80,
quality_level: int = 1,
time_scale: float = 20.0,
target_frame_rate: int = -1,
width: Optional[int] = None,
height: Optional[int] = None,
quality_level: Optional[int] = None,
time_scale: Optional[float] = None,
target_frame_rate: Optional[int] = None,
capture_frame_rate: Optional[int] = None,
:param width: Defines the width of the display. Default 80.
:param height: Defines the height of the display. Default 80.
:param width: Defines the width of the display. (Must be set alongside height)
:param height: Defines the height of the display. (Must be set alongside width)
Default 1.
simulation but the physics might break. Default 20.
simulation but the physics might break.
specified frame rate. Default -1.
specified frame rate.
:param capture_frame_rate: Instructs the simulation to consider time between
updates to always be constant, regardless of the actual frame rate.
msg = OutgoingMessage()
msg.write_int32(width)
msg.write_int32(height)
msg.write_int32(quality_level)
msg.write_float32(time_scale)
msg.write_int32(target_frame_rate)
super().queue_message_to_send(msg)
if (width is None and height is not None) or (
width is not None and height is None
):
raise UnitySideChannelException(
"You cannot set the width/height of the screen resolution without also setting the height/width"
)
if width is not None and height is not None:
screen_msg = OutgoingMessage()
screen_msg.write_int32(self.ConfigurationType.SCREEN_RESOLUTION)
screen_msg.write_int32(width)
screen_msg.write_int32(height)
super().queue_message_to_send(screen_msg)
if quality_level is not None:
quality_level_msg = OutgoingMessage()
quality_level_msg.write_int32(self.ConfigurationType.QUALITY_LEVEL)
quality_level_msg.write_int32(quality_level)
super().queue_message_to_send(quality_level_msg)
if time_scale is not None:
time_scale_msg = OutgoingMessage()
time_scale_msg.write_int32(self.ConfigurationType.TIME_SCALE)
time_scale_msg.write_float32(time_scale)
super().queue_message_to_send(time_scale_msg)
if target_frame_rate is not None:
target_frame_rate_msg = OutgoingMessage()
target_frame_rate_msg.write_int32(self.ConfigurationType.TARGET_FRAME_RATE)
target_frame_rate_msg.write_int32(target_frame_rate)
super().queue_message_to_send(target_frame_rate_msg)
if capture_frame_rate is not None:
capture_frame_rate_msg = OutgoingMessage()
capture_frame_rate_msg.write_int32(
self.ConfigurationType.CAPTURE_FRAME_RATE
)
capture_frame_rate_msg.write_int32(capture_frame_rate)
super().queue_message_to_send(capture_frame_rate_msg)
def set_configuration(self, config: EngineConfig) -> None:
"""

5
ml-agents/mlagents/trainers/env_manager.py


def external_brains(self) -> Dict[BehaviorName, BrainParameters]:
pass
@property
@abstractmethod
def get_properties(self) -> Dict[BehaviorName, float]:
pass
@abstractmethod
def close(self):
pass

19
ml-agents/mlagents/trainers/learn.py


help="The target frame rate of the Unity environment(s). Equivalent to setting "
"Application.targetFrameRate in Unity.",
)
eng_conf.add_argument(
"--capture-frame-rate",
default=60,
type=int,
help="The capture frame rate of the Unity environment(s). Equivalent to setting "
"Time.captureFramerate in Unity.",
)
return argparser

quality_level: int = parser.get_default("quality_level")
time_scale: float = parser.get_default("time_scale")
target_frame_rate: int = parser.get_default("target_frame_rate")
capture_frame_rate: int = parser.get_default("capture_frame_rate")
@staticmethod
def from_argparse(args: argparse.Namespace) -> "RunOptions":

options.env_path, options.no_graphics, run_seed, port, options.env_args
)
engine_config = EngineConfig(
options.width,
options.height,
options.quality_level,
options.time_scale,
options.target_frame_rate,
width=options.width,
height=options.height,
quality_level=options.quality_level,
time_scale=options.time_scale,
target_frame_rate=options.target_frame_rate,
capture_frame_rate=options.capture_frame_rate,
)
env_manager = SubprocessEnvManager(env_factory, engine_config, options.num_envs)
maybe_meta_curriculum = try_create_meta_curriculum(

14
ml-agents/mlagents/trainers/simple_env_manager.py


from mlagents_envs.timers import timed
from mlagents.trainers.action_info import ActionInfo
from mlagents.trainers.brain import BrainParameters
from mlagents_envs.side_channel.float_properties_channel import FloatPropertiesChannel
from mlagents_envs.side_channel.environment_parameters_channel import (
EnvironmentParametersChannel,
)
from mlagents.trainers.brain_conversion_utils import behavior_spec_to_brain_parameters

This is generally only useful for testing; see SubprocessEnvManager for a production-quality implementation.
"""
def __init__(self, env: BaseEnv, float_prop_channel: FloatPropertiesChannel):
def __init__(self, env: BaseEnv, env_params: EnvironmentParametersChannel):
self.shared_float_properties = float_prop_channel
self.env_params = env_params
self.env = env
self.previous_step: EnvironmentStep = EnvironmentStep.empty(0)
self.previous_all_action_info: Dict[str, ActionInfo] = {}

) -> List[EnvironmentStep]: # type: ignore
if config is not None:
for k, v in config.items():
self.shared_float_properties.set_property(k, v)
self.env_params.set_float_parameter(k, v)
self.env.reset()
all_step_result = self._generate_all_results()
self.previous_step = EnvironmentStep(all_step_result, 0, {}, {})

brain_name, self.env.get_behavior_spec(brain_name)
)
return result
@property
def get_properties(self) -> Dict[BehaviorName, float]:
return self.shared_float_properties.get_property_dict_copy()
def close(self):
self.env.close()

19
ml-agents/mlagents/trainers/subprocess_env_manager.py


)
from mlagents.trainers.brain import BrainParameters
from mlagents.trainers.action_info import ActionInfo
from mlagents_envs.side_channel.float_properties_channel import FloatPropertiesChannel
from mlagents_envs.side_channel.environment_parameters_channel import (
EnvironmentParametersChannel,
)
from mlagents_envs.side_channel.engine_configuration_channel import (
EngineConfigurationChannel,
EngineConfig,

env_factory: Callable[
[int, List[SideChannel]], UnityEnvironment
] = cloudpickle.loads(pickled_env_factory)
shared_float_properties = FloatPropertiesChannel()
env_parameters = EnvironmentParametersChannel()
engine_configuration_channel = EngineConfigurationChannel()
engine_configuration_channel.set_configuration(engine_configuration)
stats_channel = StatsSideChannel()

try:
env = env_factory(
worker_id,
[shared_float_properties, engine_configuration_channel, stats_channel],
worker_id, [env_parameters, engine_configuration_channel, stats_channel]
)
while True:
req: EnvironmentRequest = parent_conn.recv()

reset_timers()
elif req.cmd == EnvironmentCommand.EXTERNAL_BRAINS:
_send_response(EnvironmentCommand.EXTERNAL_BRAINS, external_brains())
elif req.cmd == EnvironmentCommand.GET_PROPERTIES:
reset_params = shared_float_properties.get_property_dict_copy()
_send_response(EnvironmentCommand.GET_PROPERTIES, reset_params)
shared_float_properties.set_property(k, v)
env_parameters.set_float_parameter(k, v)
env.reset()
all_step_result = _generate_all_results()
_send_response(EnvironmentCommand.RESET, all_step_result)

@property
def external_brains(self) -> Dict[BehaviorName, BrainParameters]:
self.env_workers[0].send(EnvironmentCommand.EXTERNAL_BRAINS)
return self.env_workers[0].recv().payload
@property
def get_properties(self) -> Dict[BehaviorName, float]:
self.env_workers[0].send(EnvironmentCommand.GET_PROPERTIES)
return self.env_workers[0].recv().payload
def close(self) -> None:

6
ml-agents/mlagents/trainers/tests/test_simple_rl.py


from mlagents.trainers.sampler_class import SamplerManager
from mlagents.trainers.demo_loader import write_demo
from mlagents.trainers.stats import StatsReporter, StatsWriter, StatsSummary
from mlagents_envs.side_channel.float_properties_channel import FloatPropertiesChannel
from mlagents_envs.side_channel.environment_parameters_channel import (
EnvironmentParametersChannel,
)
from mlagents_envs.communicator_objects.demonstration_meta_pb2 import (
DemonstrationMetaProto,
)

# Make sure threading is turned off for determinism
trainer_config["threading"] = False
if env_manager is None:
env_manager = SimpleEnvManager(env, FloatPropertiesChannel())
env_manager = SimpleEnvManager(env, EnvironmentParametersChannel())
trainer_factory = TrainerFactory(
trainer_config=trainer_config,
summaries_dir=dir,

70
com.unity.ml-agents/Runtime/EnvironmentParameters.cs


using System;
using System.Collections.Generic;
using MLAgents.SideChannels;
namespace MLAgents
{
/// <summary>
/// A container for the Environment Parameters that may be modified during training.
/// The keys for those parameters are defined in the trainer configurations and the
/// the values are generated from the training process in features such as Curriculum Learning
/// and Environment Parameter Randomization.
///
/// One current assumption for all the environment parameters is that they are of type float.
/// </summary>
public sealed class EnvironmentParameters
{
/// <summary>
/// The side channel that is used to receive the new parameter values.
/// </summary>
readonly EnvironmentParametersChannel m_Channel;
/// <summary>
/// Constructor.
/// </summary>
internal EnvironmentParameters()
{
m_Channel = new EnvironmentParametersChannel();
SideChannelsManager.RegisterSideChannel(m_Channel);
}
/// <summary>
/// Returns the parameter value for the specified key. Returns the default value provided
/// if this parameter key does not have a value. Only returns a parameter value if it is
/// of type float.
/// </summary>
/// <param name="key">The parameter key</param>
/// <param name="defaultValue">Default value for this parameter.</param>
/// <returns></returns>
public float GetWithDefault(string key, float defaultValue)
{
return m_Channel.GetWithDefault(key, defaultValue);
}
/// <summary>
/// Registers a callback action for the provided parameter key. Will overwrite any
/// existing action for that parameter. The callback will be called whenever the parameter
/// receives a value from the training process.
/// </summary>
/// <param name="key">The parameter key</param>
/// <param name="action">The callback action</param>
public void RegisterCallback(string key, Action<float> action)
{
m_Channel.RegisterCallback(key, action);
}
/// <summary>
/// Returns a list of all the parameter keys that have received values.
/// </summary>
/// <returns>List of parameter keys.</returns>
public IList<string> Keys()
{
return m_Channel.ListParameters();
}
internal void Dispose()
{
SideChannelsManager.UnregisterSideChannel(m_Channel);
}
}
}

11
com.unity.ml-agents/Runtime/EnvironmentParameters.cs.meta


fileFormatVersion: 2
guid: 90ce0b26bef35484890eac0633b85eed
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

91
com.unity.ml-agents/Runtime/SideChannels/EnvironmentParametersChannel.cs


using System.Collections.Generic;
using System;
using UnityEngine;
namespace MLAgents.SideChannels
{
/// <summary>
/// Lists the different data types supported.
/// </summary>
internal enum EnvironmentDataTypes
{
Float = 0
}
/// <summary>
/// A side channel that manages the environment parameter values from Python. Currently
/// limited to parameters of type float.
/// </summary>
internal class EnvironmentParametersChannel : SideChannel
{
Dictionary<string, float> m_Parameters = new Dictionary<string, float>();
Dictionary<string, Action<float>> m_RegisteredActions =
new Dictionary<string, Action<float>>();
const string k_EnvParamsId = "534c891e-810f-11ea-a9d0-822485860400";
/// <summary>
/// Initializes the side channel. The constructor is internal because only one instance is
/// supported at a time, and is created by the Academy.
/// </summary>
internal EnvironmentParametersChannel()
{
ChannelId = new Guid(k_EnvParamsId);
}
/// <inheritdoc/>
protected override void OnMessageReceived(IncomingMessage msg)
{
var key = msg.ReadString();
var type = msg.ReadInt32();
if ((int)EnvironmentDataTypes.Float == type)
{
var value = msg.ReadFloat32();
m_Parameters[key] = value;
Action<float> action;
m_RegisteredActions.TryGetValue(key, out action);
action?.Invoke(value);
}
else
{
Debug.LogWarning("EnvironmentParametersChannel received an unknown data type.");
}
}
/// <summary>
/// Returns the parameter value associated with the provided key. Returns the default
/// value if one doesn't exist.
/// </summary>
/// <param name="key">Parameter key.</param>
/// <param name="defaultValue">Default value to return.</param>
/// <returns></returns>
public float GetWithDefault(string key, float defaultValue)
{
float valueOut;
bool hasKey = m_Parameters.TryGetValue(key, out valueOut);
return hasKey ? valueOut : defaultValue;
}
/// <summary>
/// Registers a callback for the associated parameter key. Will overwrite any existing
/// actions for this parameter key.
/// </summary>
/// <param name="key">The parameter key.</param>
/// <param name="action">The callback.</param>
public void RegisterCallback(string key, Action<float> action)
{
m_RegisteredActions[key] = action;
}
/// <summary>
/// Returns all parameter keys that have a registered value.
/// </summary>
/// <returns></returns>
public IList<string> ListParameters()
{
return new List<string>(m_Parameters.Keys);
}
}
}

218
com.unity.ml-agents/Runtime/SideChannels/SideChannelsManager.cs


using System;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
namespace MLAgents.SideChannels
{
/// <summary>
/// Collection of static utilities for managing the registering/unregistering of
/// <see cref="SideChannels"/> and the sending/receiving of messages for all the channels.
/// </summary>
public static class SideChannelsManager
{
private static Dictionary<Guid, SideChannel> RegisteredChannels = new Dictionary<Guid, SideChannel>();
private struct CachedSideChannelMessage
{
public Guid ChannelId;
public byte[] Message;
}
private static readonly Queue<CachedSideChannelMessage> m_CachedMessages =
new Queue<CachedSideChannelMessage>();
/// <summary>
/// Register a side channel to begin sending and receiving messages. This method is
/// available for environments that have custom side channels. All built-in side
/// channels within the ML-Agents Toolkit are managed internally and do not need to
/// be explicitly registered/unregistered. A side channel may only be registered once.
/// </summary>
/// <param name="sideChannel">The side channel to register.</param>
public static void RegisterSideChannel(SideChannel sideChannel)
{
var channelId = sideChannel.ChannelId;
if (RegisteredChannels.ContainsKey(channelId))
{
throw new UnityAgentsException(
$"A side channel with id {channelId} is already registered. " +
"You cannot register multiple side channels of the same id.");
}
// Process any messages that we've already received for this channel ID.
var numMessages = m_CachedMessages.Count;
for (var i = 0; i < numMessages; i++)
{
var cachedMessage = m_CachedMessages.Dequeue();
if (channelId == cachedMessage.ChannelId)
{
sideChannel.ProcessMessage(cachedMessage.Message);
}
else
{
m_CachedMessages.Enqueue(cachedMessage);
}
}
RegisteredChannels.Add(channelId, sideChannel);
}
/// <summary>
/// Unregister a side channel to stop sending and receiving messages. This method is
/// available for environments that have custom side channels. All built-in side
/// channels within the ML-Agents Toolkit are managed internally and do not need to
/// be explicitly registered/unregistered. Unregistering a side channel that has already
/// been unregistered (or never registered in the first place) has no negative side effects.
/// Note that unregistering a side channel may not stop the Python side
/// from sending messages, but it does mean that sent messages with not result in a call
/// to <see cref="SideChannel.OnMessageReceived(IncomingMessage)"/>. Furthermore,
/// those messages will not be buffered and will, in essence, be lost.
/// </summary>
/// <param name="sideChannel">The side channel to unregister.</param>
public static void UnregisterSideChannel(SideChannel sideChannel)
{
if (RegisteredChannels.ContainsKey(sideChannel.ChannelId))
{
RegisteredChannels.Remove(sideChannel.ChannelId);
}
}
/// <summary>
/// Unregisters all the side channels from the communicator.
/// </summary>
internal static void UnregisterAllSideChannels()
{
RegisteredChannels = new Dictionary<Guid, SideChannel>();
}
/// <summary>
/// Returns the SideChannel of Type T if there is one registered, or null if it doesn't.
/// If there are multiple SideChannels of the same type registered, the returned instance is arbitrary.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
internal static T GetSideChannel<T>() where T: SideChannel
{
foreach (var sc in RegisteredChannels.Values)
{
if (sc.GetType() == typeof(T))
{
return (T) sc;
}
}
return null;
}
/// <summary>
/// Grabs the messages that the registered side channels will send to Python at the current step
/// into a singe byte array.
/// </summary>
/// <returns></returns>
internal static byte[] GetSideChannelMessage()
{
return GetSideChannelMessage(RegisteredChannels);
}
/// <summary>
/// Grabs the messages that the registered side channels will send to Python at the current step
/// into a singe byte array.
/// </summary>
/// <param name="sideChannels"> A dictionary of channel type to channel.</param>
/// <returns></returns>
internal static byte[] GetSideChannelMessage(Dictionary<Guid, SideChannel> sideChannels)
{
using (var memStream = new MemoryStream())
{
using (var binaryWriter = new BinaryWriter(memStream))
{
foreach (var sideChannel in sideChannels.Values)
{
var messageList = sideChannel.MessageQueue;
foreach (var message in messageList)
{
binaryWriter.Write(sideChannel.ChannelId.ToByteArray());
binaryWriter.Write(message.Length);
binaryWriter.Write(message);
}
sideChannel.MessageQueue.Clear();
}
return memStream.ToArray();
}
}
}
/// <summary>
/// Separates the data received from Python into individual messages for each registered side channel.
/// </summary>
/// <param name="dataReceived">The byte array of data received from Python.</param>
internal static void ProcessSideChannelData(byte[] dataReceived)
{
ProcessSideChannelData(RegisteredChannels, dataReceived);
}
/// <summary>
/// Separates the data received from Python into individual messages for each registered side channel.
/// </summary>
/// <param name="sideChannels">A dictionary of channel type to channel.</param>
/// <param name="dataReceived">The byte array of data received from Python.</param>
internal static void ProcessSideChannelData(Dictionary<Guid, SideChannel> sideChannels, byte[] dataReceived)
{
while (m_CachedMessages.Count != 0)
{
var cachedMessage = m_CachedMessages.Dequeue();
if (sideChannels.ContainsKey(cachedMessage.ChannelId))
{
sideChannels[cachedMessage.ChannelId].ProcessMessage(cachedMessage.Message);
}
else
{
Debug.Log(string.Format(
"Unknown side channel data received. Channel Id is "
+ ": {0}", cachedMessage.ChannelId));
}
}
if (dataReceived.Length == 0)
{
return;
}
using (var memStream = new MemoryStream(dataReceived))
{
using (var binaryReader = new BinaryReader(memStream))
{
while (memStream.Position < memStream.Length)
{
Guid channelId = Guid.Empty;
byte[] message = null;
try
{
channelId = new Guid(binaryReader.ReadBytes(16));
var messageLength = binaryReader.ReadInt32();
message = binaryReader.ReadBytes(messageLength);
}
catch (Exception ex)
{
throw new UnityAgentsException(
"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. Original error : "
+ ex.Message);
}
if (sideChannels.ContainsKey(channelId))
{
sideChannels[channelId].ProcessMessage(message);
}
else
{
// Don't recognize this ID, but cache it in case the SideChannel that can handle
// it is registered before the next call to ProcessSideChannelData.
m_CachedMessages.Enqueue(new CachedSideChannelMessage
{
ChannelId = channelId,
Message = message
});
}
}
}
}
}
}
}

11
com.unity.ml-agents/Runtime/SideChannels/SideChannelsManager.cs.meta


fileFormatVersion: 2
guid: ccc0d134445f947349c68a6d07e3cdc2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

71
com.unity.ml-agents/Runtime/StatsRecorder.cs


using MLAgents.SideChannels;
namespace MLAgents
{
/// <summary>
/// Determines the behavior of how multiple stats within the same summary period are combined.
/// </summary>
public enum StatAggregationMethod
{
/// <summary>
/// Values within the summary period are averaged before reporting.
/// Note that values from the same C# environment in the same step may replace each other.
/// </summary>
Average = 0,
/// <summary>
/// Only the most recent value is reported.
/// To avoid conflicts when training with multiple concurrent environments, only
/// stats from worker index 0 will be tracked.
/// </summary>
MostRecent = 1
}
/// <summary>
/// Add stats (key-value pairs) for reporting. These values will sent these to a StatsReporter
/// instance, which means the values will appear in the TensorBoard summary, as well as trainer
/// gauges. You can nest stats in TensorBoard by adding "/" in the name (e.g. "Agent/Health"
/// and "Agent/Wallet"). Note that stats are only written to TensorBoard each summary_frequency
/// steps (a trainer configuration). If a stat is received multiple times, within that period
/// then the values will be aggregated using the <see cref="StatAggregationMethod"/> provided.
/// </summary>
public sealed class StatsRecorder
{
/// <summary>
/// The side channel that is used to receive the new parameter values.
/// </summary>
readonly StatsSideChannel m_Channel;
/// <summary>
/// Constructor.
/// </summary>
internal StatsRecorder()
{
m_Channel = new StatsSideChannel();
SideChannelsManager.RegisterSideChannel(m_Channel);
}
/// <summary>
/// Add a stat value for reporting.
/// </summary>
/// <param name="key">The stat name.</param>
/// <param name="value">
/// The stat value. You can nest stats in TensorBoard by using "/".
/// </param>
/// <param name="aggregationMethod">
/// How multiple values sent in the same summary window should be treated.
/// </param>
public void Add(
string key,
float value,
StatAggregationMethod aggregationMethod = StatAggregationMethod.Average)
{
m_Channel.AddStat(key, value, aggregationMethod);
}
internal void Dispose()
{
SideChannelsManager.UnregisterSideChannel(m_Channel);
}
}
}

11
com.unity.ml-agents/Runtime/StatsRecorder.cs.meta


fileFormatVersion: 2
guid: d9add8900e8a746e6a4cb410cb27d664
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

37
ml-agents-envs/mlagents_envs/side_channel/environment_parameters_channel.py


from mlagents_envs.side_channel import SideChannel, IncomingMessage, OutgoingMessage
from mlagents_envs.exception import UnityCommunicationException
import uuid
from enum import IntEnum
class EnvironmentParametersChannel(SideChannel):
"""
This is the SideChannel for sending environment parameters to Unity.
You can send parameters to an environment with the command
set_float_parameter.
"""
class EnvironmentDataTypes(IntEnum):
FLOAT = 0
def __init__(self) -> None:
channel_id = uuid.UUID(("534c891e-810f-11ea-a9d0-822485860400"))
super().__init__(channel_id)
def on_message_received(self, msg: IncomingMessage) -> None:
raise UnityCommunicationException(
"The EnvironmentParametersChannel received a message from Unity, "
+ "this should not have happend."
)
def set_float_parameter(self, key: str, value: float) -> None:
"""
Sets a float environment parameter in the Unity Environment.
:param key: The string identifier of the parameter.
:param value: The float value of the parameter.
"""
msg = OutgoingMessage()
msg.write_string(key)
msg.write_int32(self.EnvironmentDataTypes.FLOAT)
msg.write_float32(value)
super().queue_message_to_send(msg)

238
com.unity.ml-agents/Runtime/SideChannels/SideChannelUtils.cs


using System;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
namespace MLAgents.SideChannels
{
/// <summary>
/// Collection of static utilities for managing the registering/unregistering of
/// <see cref="SideChannels"/> and the sending/receiving of messages for all the channels.
/// </summary>
public static class SideChannelUtils
{
private static Dictionary<Guid, SideChannel> RegisteredChannels = new Dictionary<Guid, SideChannel>();
private struct CachedSideChannelMessage
{
public Guid ChannelId;
public byte[] Message;
}
private static Queue<CachedSideChannelMessage> m_CachedMessages = new Queue<CachedSideChannelMessage>();
/// <summary>
/// Registers a side channel to the communicator. The side channel will exchange
/// messages with its Python equivalent.
/// </summary>
/// <param name="sideChannel"> The side channel to be registered.</param>
public static void RegisterSideChannel(SideChannel sideChannel)
{
var channelId = sideChannel.ChannelId;
if (RegisteredChannels.ContainsKey(channelId))
{
throw new UnityAgentsException(string.Format(
"A side channel with type index {0} is already registered. You cannot register multiple " +
"side channels of the same id.", channelId));
}
// Process any messages that we've already received for this channel ID.
var numMessages = m_CachedMessages.Count;
for (int i = 0; i < numMessages; i++)
{
var cachedMessage = m_CachedMessages.Dequeue();
if (channelId == cachedMessage.ChannelId)
{
using (var incomingMsg = new IncomingMessage(cachedMessage.Message))
{
sideChannel.OnMessageReceived(incomingMsg);
}
}
else
{
m_CachedMessages.Enqueue(cachedMessage);
}
}
RegisteredChannels.Add(channelId, sideChannel);
}
/// <summary>
/// Unregisters a side channel from the communicator.
/// </summary>
/// <param name="sideChannel"> The side channel to be unregistered.</param>
public static void UnregisterSideChannel(SideChannel sideChannel)
{
if (RegisteredChannels.ContainsKey(sideChannel.ChannelId))
{
RegisteredChannels.Remove(sideChannel.ChannelId);
}
}
/// <summary>
/// Unregisters all the side channels from the communicator.
/// </summary>
public static void UnregisterAllSideChannels()
{
RegisteredChannels = new Dictionary<Guid, SideChannel>();
}
/// <summary>
/// Returns the SideChannel of Type T if there is one registered, or null if it doesn't.
/// If there are multiple SideChannels of the same type registered, the returned instance is arbitrary.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static T GetSideChannel<T>() where T: SideChannel
{
foreach (var sc in RegisteredChannels.Values)
{
if (sc.GetType() == typeof(T))
{
return (T) sc;
}
}
return null;
}
/// <summary>
/// Returns all SideChannels of Type T that are registered. Use <see cref="GetSideChannel{T}()"/> if possible,
/// as that does not make any memory allocations.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static List<T> GetSideChannels<T>() where T: SideChannel
{
var output = new List<T>();
foreach (var sc in RegisteredChannels.Values)
{
if (sc.GetType() == typeof(T))
{
output.Add((T) sc);
}
}
return output;
}
/// <summary>
/// Grabs the messages that the registered side channels will send to Python at the current step
/// into a singe byte array.
/// </summary>
/// <returns></returns>
internal static byte[] GetSideChannelMessage()
{
return GetSideChannelMessage(RegisteredChannels);
}
/// <summary>
/// Grabs the messages that the registered side channels will send to Python at the current step
/// into a singe byte array.
/// </summary>
/// <param name="sideChannels"> A dictionary of channel type to channel.</param>
/// <returns></returns>
internal static byte[] GetSideChannelMessage(Dictionary<Guid, SideChannel> sideChannels)
{
using (var memStream = new MemoryStream())
{
using (var binaryWriter = new BinaryWriter(memStream))
{
foreach (var sideChannel in sideChannels.Values)
{
var messageList = sideChannel.MessageQueue;
foreach (var message in messageList)
{
binaryWriter.Write(sideChannel.ChannelId.ToByteArray());
binaryWriter.Write(message.Length);
binaryWriter.Write(message);
}
sideChannel.MessageQueue.Clear();
}
return memStream.ToArray();
}
}
}
/// <summary>
/// Separates the data received from Python into individual messages for each registered side channel.
/// </summary>
/// <param name="dataReceived">The byte array of data received from Python.</param>
internal static void ProcessSideChannelData(byte[] dataReceived)
{
ProcessSideChannelData(RegisteredChannels, dataReceived);
}
/// <summary>
/// Separates the data received from Python into individual messages for each registered side channel.
/// </summary>
/// <param name="sideChannels">A dictionary of channel type to channel.</param>
/// <param name="dataReceived">The byte array of data received from Python.</param>
internal static void ProcessSideChannelData(Dictionary<Guid, SideChannel> sideChannels, byte[] dataReceived)
{
while (m_CachedMessages.Count != 0)
{
var cachedMessage = m_CachedMessages.Dequeue();
if (sideChannels.ContainsKey(cachedMessage.ChannelId))
{
using (var incomingMsg = new IncomingMessage(cachedMessage.Message))
{
sideChannels[cachedMessage.ChannelId].OnMessageReceived(incomingMsg);
}
}
else
{
Debug.Log(string.Format(
"Unknown side channel data received. Channel Id is "
+ ": {0}", cachedMessage.ChannelId));
}
}
if (dataReceived.Length == 0)
{
return;
}
using (var memStream = new MemoryStream(dataReceived))
{
using (var binaryReader = new BinaryReader(memStream))
{
while (memStream.Position < memStream.Length)
{
Guid channelId = Guid.Empty;
byte[] message = null;
try
{
channelId = new Guid(binaryReader.ReadBytes(16));
var messageLength = binaryReader.ReadInt32();
message = binaryReader.ReadBytes(messageLength);
}
catch (Exception ex)
{
throw new UnityAgentsException(
"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. Original error : "
+ ex.Message);
}
if (sideChannels.ContainsKey(channelId))
{
using (var incomingMsg = new IncomingMessage(message))
{
sideChannels[channelId].OnMessageReceived(incomingMsg);
}
}
else
{
// Don't recognize this ID, but cache it in case the SideChannel that can handle
// it is registered before the next call to ProcessSideChannelData.
m_CachedMessages.Enqueue(new CachedSideChannelMessage
{
ChannelId = channelId,
Message = message
});
}
}
}
}
}
}
}

/com.unity.ml-agents/Runtime/SideChannels/SideChannelUtils.cs.meta → /com.unity.ml-agents/Runtime/SideChannels/EnvironmentParametersChannel.cs.meta

正在加载...
取消
保存