diff --git a/com.unity.ml-agents.tests/.buginfo b/com.unity.ml-agents.tests/.buginfo new file mode 100644 index 0000000000000000000000000000000000000000..4ff252b886e0590d4d9e7ba2a721be0301d1b5f8 --- /dev/null +++ b/com.unity.ml-agents.tests/.buginfo @@ -0,0 +1,5 @@ +system: jira +server: jira.unity3d.com +issuetype: Bug +project: UUM +package: ML Agents diff --git a/com.unity.ml-agents.tests/Tests/Editor/AcademyTests.cs b/com.unity.ml-agents.tests/Tests/Editor/AcademyTests.cs new file mode 100644 index 0000000000000000000000000000000000000000..0e1d08014d08e4a9e8984e6e2b0dd3c9cf21d086 --- /dev/null +++ b/com.unity.ml-agents.tests/Tests/Editor/AcademyTests.cs @@ -0,0 +1,50 @@ +using NUnit.Framework; +using Unity.MLAgents.Sensors; +using UnityEngine; + +namespace Unity.MLAgents.Tests +{ + [TestFixture] + public class AcademyTests + { + [Test] + public void TestPackageVersion() + { + var packageInfo = UnityEditor.PackageManager.PackageInfo.FindForAssembly(typeof(Agent).Assembly); + Assert.AreEqual("com.unity.ml-agents", packageInfo.name); + Assert.AreEqual(Academy.k_PackageVersion, packageInfo.version); + } + + class RecursiveAgent : Agent + { + int m_collectObsCount; + public override void CollectObservations(VectorSensor sensor) + { + m_collectObsCount++; + if (m_collectObsCount == 1) + { + // NEVER DO THIS IN REAL CODE! + Academy.Instance.EnvironmentStep(); + } + } + } + + [Test] + public void TestRecursiveStepThrows() + { + var gameObj = new GameObject(); + var agent = gameObj.AddComponent(); + agent.Awake(); + agent.LazyInitialize(); + agent.RequestDecision(); + + Assert.Throws(() => + { + Academy.Instance.EnvironmentStep(); + }); + + // Make sure the Academy reset to a good state and is still steppable. + Academy.Instance.EnvironmentStep(); + } + } +} diff --git a/com.unity.ml-agents.tests/Tests/Editor/AcademyTests.cs.meta b/com.unity.ml-agents.tests/Tests/Editor/AcademyTests.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..fa65ced67da8c59f2f0fb34e71ba01aeecd792d6 Binary files /dev/null and b/com.unity.ml-agents.tests/Tests/Editor/AcademyTests.cs.meta differ diff --git a/com.unity.ml-agents.tests/Tests/Editor/Actuators.meta b/com.unity.ml-agents.tests/Tests/Editor/Actuators.meta new file mode 100644 index 0000000000000000000000000000000000000000..5c6399dc6c3d6aeb56c4272ee183fb12d0476285 Binary files /dev/null and b/com.unity.ml-agents.tests/Tests/Editor/Actuators.meta differ diff --git a/com.unity.ml-agents.tests/Tests/Editor/Actuators/ActionSegmentTests.cs b/com.unity.ml-agents.tests/Tests/Editor/Actuators/ActionSegmentTests.cs new file mode 100644 index 0000000000000000000000000000000000000000..6fec579a3450dc7b3fd0c29d0a57b15fb5995e4a --- /dev/null +++ b/com.unity.ml-agents.tests/Tests/Editor/Actuators/ActionSegmentTests.cs @@ -0,0 +1,62 @@ +using System; +using NUnit.Framework; +using Unity.MLAgents.Actuators; + +namespace Unity.MLAgents.Tests.Actuators +{ + [TestFixture] + public class ActionSegmentTests + { + [Test] + public void TestConstruction() + { + var floatArray = new[] { 1f, 2f, 3f, 4f, 5f, 6f, 7f }; + Assert.Throws( + () => new ActionSegment(floatArray, 100, 1)); + + var segment = new ActionSegment(Array.Empty(), 0, 0); + Assert.AreEqual(segment, ActionSegment.Empty); + } + + [Test] + public void TestIndexing() + { + var floatArray = new[] { 1f, 2f, 3f, 4f, 5f, 6f, 7f }; + for (var i = 0; i < floatArray.Length; i++) + { + var start = 0 + i; + var length = floatArray.Length - i; + var actionSegment = new ActionSegment(floatArray, start, length); + for (var j = 0; j < actionSegment.Length; j++) + { + Assert.AreEqual(actionSegment[j], floatArray[start + j]); + } + } + } + + [Test] + public void TestEnumerator() + { + var floatArray = new[] { 1f, 2f, 3f, 4f, 5f, 6f, 7f }; + for (var i = 0; i < floatArray.Length; i++) + { + var start = 0 + i; + var length = floatArray.Length - i; + var actionSegment = new ActionSegment(floatArray, start, length); + var j = 0; + foreach (var item in actionSegment) + { + Assert.AreEqual(item, floatArray[start + j++]); + } + } + } + + [Test] + public void TestNullConstructor() + { + var actionSegment = new ActionSegment(null); + Assert.IsTrue(actionSegment.Length == 0); + Assert.IsTrue(actionSegment.Array == Array.Empty()); + } + } +} diff --git a/com.unity.ml-agents.tests/Tests/Editor/Actuators/ActionSegmentTests.cs.meta b/com.unity.ml-agents.tests/Tests/Editor/Actuators/ActionSegmentTests.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..2332580c178ce327e313cabaa807581f8050e243 Binary files /dev/null and b/com.unity.ml-agents.tests/Tests/Editor/Actuators/ActionSegmentTests.cs.meta differ diff --git a/com.unity.ml-agents.tests/Tests/Editor/Actuators/ActionSpecTests.cs b/com.unity.ml-agents.tests/Tests/Editor/Actuators/ActionSpecTests.cs new file mode 100644 index 0000000000000000000000000000000000000000..09dfd336705b08c2ed2041a74fbcc36852fa1ebd --- /dev/null +++ b/com.unity.ml-agents.tests/Tests/Editor/Actuators/ActionSpecTests.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using Unity.MLAgents.Actuators; + +namespace Unity.MLAgents.Tests.Actuators +{ + [TestFixture] + public class ActionSpecTests + { + [Test] + public void ActionSpecCombineTest() + { + var as0 = new ActionSpec(3, new[] { 3, 2, 1 }); + var as1 = new ActionSpec(1, new[] { 35, 122, 1, 3, 8, 3 }); + + var as0NumCon = 3; + var as0NumDis = as0.NumDiscreteActions; + var as1NumCon = 1; + var as1NumDis = as1.NumDiscreteActions; + var branchSizes = new List(); + branchSizes.AddRange(as0.BranchSizes); + branchSizes.AddRange(as1.BranchSizes); + + var asc = ActionSpec.Combine(as0, as1); + + Assert.AreEqual(as0NumCon + as1NumCon, asc.NumContinuousActions); + Assert.AreEqual(as0NumDis + as1NumDis, asc.NumDiscreteActions); + Assert.IsTrue(branchSizes.ToArray().SequenceEqual(asc.BranchSizes)); + + as0 = new ActionSpec(3); + as1 = new ActionSpec(1); + asc = ActionSpec.Combine(as0, as1); + Assert.IsEmpty(asc.BranchSizes); + } + } +} diff --git a/com.unity.ml-agents.tests/Tests/Editor/Actuators/ActionSpecTests.cs.meta b/com.unity.ml-agents.tests/Tests/Editor/Actuators/ActionSpecTests.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..18ebcbb881521f2d42f3bdeb85a59f9f39196df5 Binary files /dev/null and b/com.unity.ml-agents.tests/Tests/Editor/Actuators/ActionSpecTests.cs.meta differ diff --git a/com.unity.ml-agents.tests/Tests/Editor/Actuators/ActuatorDiscreteActionMaskTests.cs b/com.unity.ml-agents.tests/Tests/Editor/Actuators/ActuatorDiscreteActionMaskTests.cs new file mode 100644 index 0000000000000000000000000000000000000000..1c486af483693c7bbdf08a8a307afe3a2f933ee1 --- /dev/null +++ b/com.unity.ml-agents.tests/Tests/Editor/Actuators/ActuatorDiscreteActionMaskTests.cs @@ -0,0 +1,136 @@ +using System.Collections.Generic; +using NUnit.Framework; +using Unity.MLAgents.Actuators; + +namespace Unity.MLAgents.Tests.Actuators +{ + [TestFixture] + public class ActuatorDiscreteActionMaskTests + { + [Test] + public void Construction() + { + var masker = new ActuatorDiscreteActionMask(new List(), 0, 0); + Assert.IsNotNull(masker); + } + + [Test] + public void NullMask() + { + var masker = new ActuatorDiscreteActionMask(new List(), 0, 0); + var mask = masker.GetMask(); + Assert.IsNull(mask); + } + + [Test] + public void FirstBranchMask() + { + var actuator1 = new TestActuator(ActionSpec.MakeDiscrete(new[] { 4, 5, 6 }), "actuator1"); + var masker = new ActuatorDiscreteActionMask(new IActuator[] { actuator1 }, 15, 3); + var mask = masker.GetMask(); + Assert.IsNull(mask); + masker.SetActionEnabled(0, 1, false); + masker.SetActionEnabled(0, 2, false); + masker.SetActionEnabled(0, 3, false); + mask = masker.GetMask(); + Assert.IsFalse(mask[0]); + Assert.IsTrue(mask[1]); + Assert.IsTrue(mask[2]); + Assert.IsTrue(mask[3]); + Assert.IsFalse(mask[4]); + Assert.AreEqual(mask.Length, 15); + } + + [Test] + public void CanOverwriteMask() + { + var actuator1 = new TestActuator(ActionSpec.MakeDiscrete(new[] { 4, 5, 6 }), "actuator1"); + var masker = new ActuatorDiscreteActionMask(new IActuator[] { actuator1 }, 15, 3); + masker.SetActionEnabled(0, 1, false); + var mask = masker.GetMask(); + Assert.IsTrue(mask[1]); + + masker.SetActionEnabled(0, 1, true); + Assert.IsFalse(mask[1]); + } + + [Test] + public void SecondBranchMask() + { + var actuator1 = new TestActuator(ActionSpec.MakeDiscrete(new[] { 4, 5, 6 }), "actuator1"); + var masker = new ActuatorDiscreteActionMask(new[] { actuator1 }, 15, 3); + masker.SetActionEnabled(1, 1, false); + masker.SetActionEnabled(1, 2, false); + masker.SetActionEnabled(1, 3, false); + var mask = masker.GetMask(); + Assert.IsFalse(mask[0]); + Assert.IsFalse(mask[4]); + Assert.IsTrue(mask[5]); + Assert.IsTrue(mask[6]); + Assert.IsTrue(mask[7]); + Assert.IsFalse(mask[8]); + Assert.IsFalse(mask[9]); + } + + [Test] + public void MaskReset() + { + var actuator1 = new TestActuator(ActionSpec.MakeDiscrete(new[] { 4, 5, 6 }), "actuator1"); + var masker = new ActuatorDiscreteActionMask(new IActuator[] { actuator1 }, 15, 3); + masker.SetActionEnabled(1, 1, false); + masker.SetActionEnabled(1, 2, false); + masker.SetActionEnabled(1, 3, false); + masker.ResetMask(); + var mask = masker.GetMask(); + for (var i = 0; i < 15; i++) + { + Assert.IsFalse(mask[i]); + } + } + + [Test] + public void ThrowsError() + { + var actuator1 = new TestActuator(ActionSpec.MakeDiscrete(new[] { 4, 5, 6 }), "actuator1"); + var masker = new ActuatorDiscreteActionMask(new IActuator[] { actuator1 }, 15, 3); + Assert.Catch( + () => masker.SetActionEnabled(0, 5, false)); + Assert.Catch( + () => masker.SetActionEnabled(1, 5, false)); + masker.SetActionEnabled(2, 5, false); + Assert.Catch( + () => masker.SetActionEnabled(3, 1, false)); + masker.GetMask(); + masker.ResetMask(); + masker.SetActionEnabled(0, 0, false); + masker.SetActionEnabled(0, 1, false); + masker.SetActionEnabled(0, 2, false); + masker.SetActionEnabled(0, 3, false); + Assert.Catch( + () => masker.GetMask()); + } + + [Test] + public void MultipleMaskEdit() + { + var actuator1 = new TestActuator(ActionSpec.MakeDiscrete(new[] { 4, 5, 6 }), "actuator1"); + var masker = new ActuatorDiscreteActionMask(new IActuator[] { actuator1 }, 15, 3); + masker.SetActionEnabled(0, 0, false); + masker.SetActionEnabled(0, 1, false); + masker.SetActionEnabled(0, 3, false); + masker.SetActionEnabled(2, 1, false); + var mask = masker.GetMask(); + for (var i = 0; i < 15; i++) + { + if ((i == 0) || (i == 1) || (i == 3) || (i == 10)) + { + Assert.IsTrue(mask[i]); + } + else + { + Assert.IsFalse(mask[i]); + } + } + } + } +} diff --git a/com.unity.ml-agents.tests/Tests/Editor/Actuators/ActuatorDiscreteActionMaskTests.cs.meta b/com.unity.ml-agents.tests/Tests/Editor/Actuators/ActuatorDiscreteActionMaskTests.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..a5dd1f3ad99ff8f4b5ea3a08e64de9f48d02d464 Binary files /dev/null and b/com.unity.ml-agents.tests/Tests/Editor/Actuators/ActuatorDiscreteActionMaskTests.cs.meta differ diff --git a/com.unity.ml-agents.tests/Tests/Editor/Actuators/ActuatorManagerTests.cs b/com.unity.ml-agents.tests/Tests/Editor/Actuators/ActuatorManagerTests.cs new file mode 100644 index 0000000000000000000000000000000000000000..63791b303cfd30b98d2d47ecf768cfb036f02ec8 --- /dev/null +++ b/com.unity.ml-agents.tests/Tests/Editor/Actuators/ActuatorManagerTests.cs @@ -0,0 +1,352 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using NUnit.Framework; +using Unity.MLAgents.Actuators; +using UnityEngine; +using UnityEngine.TestTools; +using Assert = UnityEngine.Assertions.Assert; + +namespace Unity.MLAgents.Tests.Actuators +{ + [TestFixture] + public class ActuatorManagerTests + { + [Test] + public void TestEnsureBufferSizeContinuous() + { + var manager = new ActuatorManager(); + var actuator1 = new TestActuator(ActionSpec.MakeContinuous(10), "actuator1"); + var actuator2 = new TestActuator(ActionSpec.MakeContinuous(2), "actuator2"); + manager.Add(actuator1); + manager.Add(actuator2); + var actuator1ActionSpaceDef = actuator1.ActionSpec; + var actuator2ActionSpaceDef = actuator2.ActionSpec; + manager.ReadyActuatorsForExecution(new[] { actuator1, actuator2 }, + actuator1ActionSpaceDef.NumContinuousActions + actuator2ActionSpaceDef.NumContinuousActions, + actuator1ActionSpaceDef.SumOfDiscreteBranchSizes + actuator2ActionSpaceDef.SumOfDiscreteBranchSizes, + actuator1ActionSpaceDef.NumDiscreteActions + actuator2ActionSpaceDef.NumDiscreteActions); + + manager.UpdateActions(new ActionBuffers(new[] + { 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f, 9f, 10f, 11f }, Array.Empty())); + + Assert.IsTrue(12 == manager.NumContinuousActions); + Assert.IsTrue(0 == manager.NumDiscreteActions); + Assert.IsTrue(0 == manager.SumOfDiscreteBranchSizes); + Assert.IsTrue(12 == manager.StoredActions.ContinuousActions.Length); + Assert.IsTrue(0 == manager.StoredActions.DiscreteActions.Length); + } + + [Test] + public void TestEnsureBufferDiscrete() + { + var manager = new ActuatorManager(); + var actuator1 = new TestActuator(ActionSpec.MakeDiscrete(new[] { 1, 2, 3, 4 }), "actuator1"); + var actuator2 = new TestActuator(ActionSpec.MakeDiscrete(new[] { 1, 1, 1 }), "actuator2"); + manager.Add(actuator1); + manager.Add(actuator2); + var actuator1ActionSpaceDef = actuator1.ActionSpec; + var actuator2ActionSpaceDef = actuator2.ActionSpec; + manager.ReadyActuatorsForExecution(new[] { actuator1, actuator2 }, + actuator1ActionSpaceDef.NumContinuousActions + actuator2ActionSpaceDef.NumContinuousActions, + actuator1ActionSpaceDef.SumOfDiscreteBranchSizes + actuator2ActionSpaceDef.SumOfDiscreteBranchSizes, + actuator1ActionSpaceDef.NumDiscreteActions + actuator2ActionSpaceDef.NumDiscreteActions); + + manager.UpdateActions(new ActionBuffers(Array.Empty(), + new[] { 0, 1, 2, 3, 4, 5, 6 })); + + Assert.IsTrue(0 == manager.NumContinuousActions); + Assert.IsTrue(7 == manager.NumDiscreteActions); + Assert.IsTrue(13 == manager.SumOfDiscreteBranchSizes); + Assert.IsTrue(0 == manager.StoredActions.ContinuousActions.Length); + Assert.IsTrue(7 == manager.StoredActions.DiscreteActions.Length); + } + + [Test] + public void TestAllowMixedActions() + { + // Make sure discrete + continuous actuators are allowed. + var manager = new ActuatorManager(); + var actuator1 = new TestActuator(ActionSpec.MakeDiscrete(new[] { 1, 2, 3, 4 }), "actuator1"); + var actuator2 = new TestActuator(ActionSpec.MakeContinuous(3), "actuator2"); + manager.Add(actuator1); + manager.Add(actuator2); + manager.ReadyActuatorsForExecution(new[] { actuator1, actuator2 }, 3, 10, 4); + } + + [Test] + public void TestFailOnSameActuatorName() + { + var manager = new ActuatorManager(); + var actuator1 = new TestActuator(ActionSpec.MakeContinuous(3), "actuator1"); + var actuator2 = new TestActuator(ActionSpec.MakeContinuous(3), "actuator1"); + manager.Add(actuator1); + manager.Add(actuator2); + manager.ReadyActuatorsForExecution(new[] { actuator1, actuator2 }, 3, 10, 4); + LogAssert.Expect(LogType.Assert, "Actuator names must be unique."); + } + + [Test] + public void TestExecuteActionsDiscrete() + { + var manager = new ActuatorManager(); + var actuator1 = new TestActuator(ActionSpec.MakeDiscrete(new[] { 1, 2, 3, 4 }), "actuator1"); + var actuator2 = new TestActuator(ActionSpec.MakeDiscrete(new[] { 1, 1, 1 }), "actuator2"); + manager.Add(actuator1); + manager.Add(actuator2); + + var discreteActionBuffer = new[] { 0, 1, 2, 3, 4, 5, 6 }; + manager.UpdateActions(new ActionBuffers(Array.Empty(), + discreteActionBuffer)); + + manager.ExecuteActions(); + var actuator1Actions = actuator1.LastActionBuffer.DiscreteActions; + var actuator2Actions = actuator2.LastActionBuffer.DiscreteActions; + TestSegmentEquality(actuator1Actions, discreteActionBuffer); TestSegmentEquality(actuator2Actions, discreteActionBuffer); + } + + [Test] + public void TestExecuteActionsContinuous() + { + var manager = new ActuatorManager(); + var actuator1 = new TestActuator(ActionSpec.MakeContinuous(3), + "actuator1"); + var actuator2 = new TestActuator(ActionSpec.MakeContinuous(3), "actuator2"); + manager.Add(actuator1); + manager.Add(actuator2); + + var continuousActionBuffer = new[] { 0f, 1f, 2f, 3f, 4f, 5f }; + manager.UpdateActions(new ActionBuffers(continuousActionBuffer, + Array.Empty())); + + manager.ExecuteActions(); + var actuator1Actions = actuator1.LastActionBuffer.ContinuousActions; + var actuator2Actions = actuator2.LastActionBuffer.ContinuousActions; + TestSegmentEquality(actuator1Actions, continuousActionBuffer); + TestSegmentEquality(actuator2Actions, continuousActionBuffer); + } + + static void TestSegmentEquality(ActionSegment actionSegment, T[] actionBuffer) + where T : struct + { + Assert.IsFalse(actionSegment.Length == 0); + for (var i = 0; i < actionSegment.Length; i++) + { + var action = actionSegment[i]; + Assert.AreEqual(action, actionBuffer[actionSegment.Offset + i]); + } + } + + [Test] + public void TestUpdateActionsContinuous() + { + var manager = new ActuatorManager(); + var actuator1 = new TestActuator(ActionSpec.MakeContinuous(3), + "actuator1"); + var actuator2 = new TestActuator(ActionSpec.MakeContinuous(3), "actuator2"); + manager.Add(actuator1); + manager.Add(actuator2); + var continuousActionBuffer = new[] { 0f, 1f, 2f, 3f, 4f, 5f }; + manager.UpdateActions(new ActionBuffers(continuousActionBuffer, + Array.Empty())); + + Assert.IsTrue(manager.StoredActions.ContinuousActions.SequenceEqual(continuousActionBuffer)); + } + + [Test] + public void TestUpdateActionsDiscrete() + { + var manager = new ActuatorManager(); + var actuator1 = new TestActuator(ActionSpec.MakeDiscrete(new[] { 1, 2, 3 }), + "actuator1"); + var actuator2 = new TestActuator(ActionSpec.MakeDiscrete(new[] { 1, 2, 3 }), "actuator2"); + manager.Add(actuator1); + manager.Add(actuator2); + var discreteActionBuffer = new[] { 0, 1, 2, 3, 4, 5 }; + manager.UpdateActions(new ActionBuffers(Array.Empty(), + discreteActionBuffer)); + + Debug.Log(manager.StoredActions.DiscreteActions); + Debug.Log(discreteActionBuffer); + Assert.IsTrue(manager.StoredActions.DiscreteActions.SequenceEqual(discreteActionBuffer)); + } + + [Test] + public void TestRemove() + { + var manager = new ActuatorManager(); + var actuator1 = new TestActuator(ActionSpec.MakeDiscrete(new[] { 1, 2, 3 }), + "actuator1"); + var actuator2 = new TestActuator(ActionSpec.MakeDiscrete(new[] { 1, 2, 3 }), "actuator2"); + + manager.Add(actuator1); + manager.Add(actuator2); + Assert.IsTrue(manager.NumDiscreteActions == 6); + Assert.IsTrue(manager.SumOfDiscreteBranchSizes == 12); + + manager.Remove(actuator2); + + Assert.IsTrue(manager.NumDiscreteActions == 3); + Assert.IsTrue(manager.SumOfDiscreteBranchSizes == 6); + + manager.Remove(null); + + Assert.IsTrue(manager.NumDiscreteActions == 3); + Assert.IsTrue(manager.SumOfDiscreteBranchSizes == 6); + + manager.RemoveAt(0); + Assert.IsTrue(manager.NumDiscreteActions == 0); + Assert.IsTrue(manager.SumOfDiscreteBranchSizes == 0); + } + + [Test] + public void TestClear() + { + var manager = new ActuatorManager(); + var actuator1 = new TestActuator(ActionSpec.MakeDiscrete(new[] { 1, 2, 3 }), + "actuator1"); + var actuator2 = new TestActuator(ActionSpec.MakeDiscrete(new[] { 1, 2, 3 }), "actuator2"); + manager.Add(actuator1); + manager.Add(actuator2); + + Assert.IsTrue(manager.NumDiscreteActions == 6); + Assert.IsTrue(manager.SumOfDiscreteBranchSizes == 12); + + manager.Clear(); + + Assert.IsTrue(manager.NumDiscreteActions == 0); + Assert.IsTrue(manager.SumOfDiscreteBranchSizes == 0); + } + + [Test] + public void TestIndexSet() + { + var manager = new ActuatorManager(); + var actuator1 = new TestActuator(ActionSpec.MakeDiscrete(new[] { 1, 2, 3, 4 }), + "actuator1"); + var actuator2 = new TestActuator(ActionSpec.MakeDiscrete(new[] { 1, 2, 3 }), "actuator2"); + manager.Add(actuator1); + Assert.IsTrue(manager.NumDiscreteActions == 4); + Assert.IsTrue(manager.SumOfDiscreteBranchSizes == 10); + manager[0] = actuator2; + Assert.IsTrue(manager.NumDiscreteActions == 3); + Assert.IsTrue(manager.SumOfDiscreteBranchSizes == 6); + } + + [Test] + public void TestInsert() + { + var manager = new ActuatorManager(); + var actuator1 = new TestActuator(ActionSpec.MakeDiscrete(new[] { 1, 2, 3, 4 }), + "actuator1"); + var actuator2 = new TestActuator(ActionSpec.MakeDiscrete(new[] { 1, 2, 3 }), "actuator2"); + manager.Add(actuator1); + Assert.IsTrue(manager.NumDiscreteActions == 4); + Assert.IsTrue(manager.SumOfDiscreteBranchSizes == 10); + manager.Insert(0, actuator2); + Assert.IsTrue(manager.NumDiscreteActions == 7); + Assert.IsTrue(manager.SumOfDiscreteBranchSizes == 16); + Assert.IsTrue(manager.IndexOf(actuator2) == 0); + } + + [Test] + public void TestResetData() + { + var manager = new ActuatorManager(); + var actuator1 = new TestActuator(ActionSpec.MakeContinuous(3), + "actuator1"); + var actuator2 = new TestActuator(ActionSpec.MakeContinuous(3), "actuator2"); + manager.Add(actuator1); + manager.Add(actuator2); + var continuousActionBuffer = new[] { 0f, 1f, 2f, 3f, 4f, 5f }; + manager.UpdateActions(new ActionBuffers(continuousActionBuffer, + Array.Empty())); + + Assert.IsTrue(manager.StoredActions.ContinuousActions.SequenceEqual(continuousActionBuffer)); + Assert.IsTrue(manager.NumContinuousActions == 6); + manager.ResetData(); + + Assert.IsTrue(manager.StoredActions.ContinuousActions.SequenceEqual(new[] { 0f, 0f, 0f, 0f, 0f, 0f })); + } + + [Test] + public void TestWriteDiscreteActionMask() + { + var manager = new ActuatorManager(2); + var va1 = new TestActuator(ActionSpec.MakeDiscrete(new[] { 1, 2, 3 }), "name"); + var va2 = new TestActuator(ActionSpec.MakeDiscrete(new[] { 3, 2, 1 }), "name1"); + manager.Add(va1); + manager.Add(va2); + + var groundTruthMask = new[] + { + false, + true, false, + false, true, true, + true, false, true, + false, true, + false + }; + + va1.Masks = new[] + { + Array.Empty(), + new[] { 0 }, + new[] { 1, 2 } + }; + + va2.Masks = new[] + { + new[] {0, 2}, + new[] {1}, + Array.Empty() + }; + manager.WriteActionMask(); + Assert.IsTrue(groundTruthMask.SequenceEqual(manager.DiscreteActionMask.GetMask())); + } + + [Test] + public void TestHeuristic() + { + var manager = new ActuatorManager(2); + var va1 = new TestActuator(ActionSpec.MakeDiscrete(1, 2, 3), "name"); + var va2 = new TestActuator(ActionSpec.MakeDiscrete(3, 2, 1, 8), "name1"); + manager.Add(va1); + manager.Add(va2); + + var actionBuf = new ActionBuffers(Array.Empty(), new[] { 0, 0, 0, 0, 0, 0, 0 }); + manager.ApplyHeuristic(actionBuf); + + Assert.IsTrue(va1.m_HeuristicCalled); + Assert.AreEqual(va1.m_DiscreteBufferSize, 3); + Assert.IsTrue(va2.m_HeuristicCalled); + Assert.AreEqual(va2.m_DiscreteBufferSize, 4); + } + + /// + /// Test that sensors sort by name consistently across culture settings. + /// Example strings and cultures taken from + /// https://docs.microsoft.com/en-us/globalization/locale/sorting-and-string-comparison + /// + /// + [TestCase("da-DK")] + [TestCase("en-US")] + public void TestSortActuators(string culture) + { + List actuators = new List(); + var actuator0 = new TestActuator(ActionSpec.MakeContinuous(2), "Apple"); + var actuator1 = new TestActuator(ActionSpec.MakeContinuous(2), "Æble"); + actuators.Add(actuator0); + actuators.Add(actuator1); + + var originalCulture = CultureInfo.CurrentCulture; + CultureInfo.CurrentCulture = new CultureInfo(culture); + ActuatorManager.SortActuators(actuators); + CultureInfo.CurrentCulture = originalCulture; + + Assert.AreEqual(actuator1, actuators[0]); + Assert.AreEqual(actuator0, actuators[1]); + } + } +} diff --git a/com.unity.ml-agents.tests/Tests/Editor/Actuators/ActuatorManagerTests.cs.meta b/com.unity.ml-agents.tests/Tests/Editor/Actuators/ActuatorManagerTests.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..4946ff19fb5f374828dea3f11fc6a0bd0fe64415 Binary files /dev/null and b/com.unity.ml-agents.tests/Tests/Editor/Actuators/ActuatorManagerTests.cs.meta differ diff --git a/com.unity.ml-agents.tests/Tests/Editor/Actuators/TestActuator.cs b/com.unity.ml-agents.tests/Tests/Editor/Actuators/TestActuator.cs new file mode 100644 index 0000000000000000000000000000000000000000..643e5131b13807f9bbcb4b3e3db0ce4da27da345 --- /dev/null +++ b/com.unity.ml-agents.tests/Tests/Editor/Actuators/TestActuator.cs @@ -0,0 +1,48 @@ +using Unity.MLAgents.Actuators; +namespace Unity.MLAgents.Tests.Actuators +{ + internal class TestActuator : IActuator + { + public ActionBuffers LastActionBuffer; + public int[][] Masks; + public bool m_HeuristicCalled; + public int m_DiscreteBufferSize; + + public TestActuator(ActionSpec actuatorSpace, string name) + { + ActionSpec = actuatorSpace; + + Name = name; + } + + public void OnActionReceived(ActionBuffers actionBuffers) + { + LastActionBuffer = actionBuffers; + } + + public void WriteDiscreteActionMask(IDiscreteActionMask actionMask) + { + for (var i = 0; i < Masks.Length; i++) + { + foreach (var actionIndex in Masks[i]) + { + actionMask.SetActionEnabled(i, actionIndex, false); + } + } + } + + public ActionSpec ActionSpec { get; } + + public string Name { get; } + + public void ResetData() + { + } + + public void Heuristic(in ActionBuffers actionBuffersOut) + { + m_HeuristicCalled = true; + m_DiscreteBufferSize = actionBuffersOut.DiscreteActions.Length; + } + } +} diff --git a/com.unity.ml-agents.tests/Tests/Editor/Actuators/TestActuator.cs.meta b/com.unity.ml-agents.tests/Tests/Editor/Actuators/TestActuator.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..57e13a0e26c338b7b9f1c972351ecca373953544 Binary files /dev/null and b/com.unity.ml-agents.tests/Tests/Editor/Actuators/TestActuator.cs.meta differ diff --git a/com.unity.ml-agents.tests/Tests/Editor/Actuators/VectorActuatorTests.cs b/com.unity.ml-agents.tests/Tests/Editor/Actuators/VectorActuatorTests.cs new file mode 100644 index 0000000000000000000000000000000000000000..7fe52951c8337eb961d640751e2aa01950a569f8 --- /dev/null +++ b/com.unity.ml-agents.tests/Tests/Editor/Actuators/VectorActuatorTests.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using Unity.MLAgents.Actuators; +using Assert = UnityEngine.Assertions.Assert; + +namespace Unity.MLAgents.Tests.Actuators +{ + [TestFixture] + public class VectorActuatorTests + { + class TestActionReceiver : IActionReceiver, IHeuristicProvider + { + public ActionBuffers LastActionBuffers; + public int Branch; + public IList Mask; + public ActionSpec ActionSpec { get; } + public bool HeuristicCalled; + + public void OnActionReceived(ActionBuffers actionBuffers) + { + LastActionBuffers = actionBuffers; + } + + public void WriteDiscreteActionMask(IDiscreteActionMask actionMask) + { + foreach (var actionIndex in Mask) + { + actionMask.SetActionEnabled(Branch, actionIndex, false); + } + } + + public void Heuristic(in ActionBuffers actionBuffersOut) + { + HeuristicCalled = true; + } + } + + [Test] + public void TestConstruct() + { + var ar = new TestActionReceiver(); + var va = new VectorActuator(ar, ActionSpec.MakeDiscrete(1, 2, 3), "name"); + + Assert.IsTrue(va.ActionSpec.NumDiscreteActions == 3); + Assert.IsTrue(va.ActionSpec.SumOfDiscreteBranchSizes == 6); + Assert.IsTrue(va.ActionSpec.NumContinuousActions == 0); + + var va1 = new VectorActuator(ar, ActionSpec.MakeContinuous(4), "name"); + + Assert.IsTrue(va1.ActionSpec.NumContinuousActions == 4); + Assert.IsTrue(va1.ActionSpec.SumOfDiscreteBranchSizes == 0); + Assert.AreEqual(va1.Name, "name-Continuous"); + } + + [Test] + public void TestOnActionReceived() + { + var ar = new TestActionReceiver(); + var va = new VectorActuator(ar, ActionSpec.MakeDiscrete(1, 2, 3), "name"); + + var discreteActions = new[] { 0, 1, 1 }; + var ab = new ActionBuffers(ActionSegment.Empty, + new ActionSegment(discreteActions, 0, 3)); + + va.OnActionReceived(ab); + + Assert.AreEqual(ar.LastActionBuffers, ab); + va.ResetData(); + Assert.AreEqual(va.ActionBuffers.ContinuousActions, ActionSegment.Empty); + Assert.AreEqual(va.ActionBuffers.DiscreteActions, ActionSegment.Empty); + } + + [Test] + public void TestResetData() + { + var ar = new TestActionReceiver(); + var va = new VectorActuator(ar, ActionSpec.MakeDiscrete(1, 2, 3), "name"); + + var discreteActions = new[] { 0, 1, 1 }; + var ab = new ActionBuffers(ActionSegment.Empty, + new ActionSegment(discreteActions, 0, 3)); + + va.OnActionReceived(ab); + } + + [Test] + public void TestWriteDiscreteActionMask() + { + var ar = new TestActionReceiver(); + var va = new VectorActuator(ar, ActionSpec.MakeDiscrete(1, 2, 3), "name"); + var bdam = new ActuatorDiscreteActionMask(new[] { va }, 6, 3); + + var groundTruthMask = new[] { false, true, false, false, true, true }; + + ar.Branch = 1; + ar.Mask = new[] { 0 }; + va.WriteDiscreteActionMask(bdam); + ar.Branch = 2; + ar.Mask = new[] { 1, 2 }; + va.WriteDiscreteActionMask(bdam); + + Assert.IsTrue(groundTruthMask.SequenceEqual(bdam.GetMask())); + } + + [Test] + public void TestHeuristic() + { + var ar = new TestActionReceiver(); + var va = new VectorActuator(ar, ActionSpec.MakeDiscrete(1, 2, 3), "name"); + + va.Heuristic(new ActionBuffers(Array.Empty(), va.ActionSpec.BranchSizes)); + Assert.IsTrue(ar.HeuristicCalled); + } + } +} diff --git a/com.unity.ml-agents.tests/Tests/Editor/Actuators/VectorActuatorTests.cs.meta b/com.unity.ml-agents.tests/Tests/Editor/Actuators/VectorActuatorTests.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..2a5a86efd011175c182fcb114237fad8197eab50 Binary files /dev/null and b/com.unity.ml-agents.tests/Tests/Editor/Actuators/VectorActuatorTests.cs.meta differ diff --git a/com.unity.ml-agents.tests/Tests/Editor/Analytics.meta b/com.unity.ml-agents.tests/Tests/Editor/Analytics.meta new file mode 100644 index 0000000000000000000000000000000000000000..473f2be08fefdf920bffb21153e933af4b6f3ab6 Binary files /dev/null and b/com.unity.ml-agents.tests/Tests/Editor/Analytics.meta differ diff --git a/com.unity.ml-agents.tests/Tests/Editor/Analytics/InferenceAnalyticsTests.cs b/com.unity.ml-agents.tests/Tests/Editor/Analytics/InferenceAnalyticsTests.cs new file mode 100644 index 0000000000000000000000000000000000000000..b7e24844a92c1eb9df9fed9952715723b35ac44c --- /dev/null +++ b/com.unity.ml-agents.tests/Tests/Editor/Analytics/InferenceAnalyticsTests.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using NUnit.Framework; +using Unity.MLAgents.Sensors; +using UnityEngine; +using Unity.InferenceEngine; +using Unity.MLAgents.Actuators; +using Unity.MLAgents.Policies; +using Unity.MLAgents.Analytics; +using UnityEditor; + + +namespace Unity.MLAgents.Tests.Analytics +{ + [TestFixture] + public class InferenceAnalyticsTests + { + const string k_continuousONNXPath = "Packages/com.unity.ml-agents.tests/Tests/Editor/TestModels/continuous2vis8vec2action_v1_0.onnx"; + ModelAsset continuousONNXModel; + Test3DSensorComponent sensor_21_20_3; + Test3DSensorComponent sensor_20_22_3; + + ActionSpec GetContinuous2vis8vec2actionActionSpec() + { + return ActionSpec.MakeContinuous(2); + } + + [SetUp] + public void SetUp() + { + if (Academy.IsInitialized) + { + Academy.Instance.Dispose(); + } + + continuousONNXModel = (ModelAsset)AssetDatabase.LoadAssetAtPath(k_continuousONNXPath, typeof(ModelAsset)); + var go = new GameObject("SensorA"); + sensor_21_20_3 = go.AddComponent(); + sensor_21_20_3.Sensor = new Test3DSensor("SensorA", 21, 20, 3); + sensor_20_22_3 = go.AddComponent(); + sensor_20_22_3.Sensor = new Test3DSensor("SensorB", 20, 22, 3); + } + + [Test] + public void TestModelEvent() + { + var sensors = new List { sensor_21_20_3.Sensor, sensor_20_22_3.Sensor }; + var behaviorName = "continuousModel"; + var actionSpec = GetContinuous2vis8vec2actionActionSpec(); + + var vectorActuator = new VectorActuator(null, actionSpec, "test'"); + var actuators = new IActuator[] { vectorActuator }; + + var continuousEvent = InferenceAnalytics.GetEventForModel( + continuousONNXModel, behaviorName, + InferenceDevice.Burst, sensors, actionSpec, + actuators + ); + + // The behavior name should be hashed, not pass-through. + Assert.AreNotEqual(behaviorName, continuousEvent.BehaviorName); + + Assert.AreEqual(2, continuousEvent.ActionSpec.NumContinuousActions); + Assert.AreEqual(0, continuousEvent.ActionSpec.NumDiscreteActions); + Assert.AreEqual(2, continuousEvent.ObservationSpecs.Count); + Assert.AreEqual(3, continuousEvent.ObservationSpecs[0].DimensionInfos.Length); + Assert.AreEqual(20, continuousEvent.ObservationSpecs[0].DimensionInfos[1].Size); + Assert.AreEqual(0, continuousEvent.ObservationSpecs[0].ObservationType); + Assert.AreEqual((int)DimensionProperty.TranslationalEquivariance, continuousEvent.ObservationSpecs[0].DimensionInfos[1].Flags); + Assert.AreEqual((int)DimensionProperty.None, continuousEvent.ObservationSpecs[0].DimensionInfos[0].Flags); + Assert.AreEqual("None", continuousEvent.ObservationSpecs[0].CompressionType); + Assert.AreEqual(Test3DSensor.k_BuiltInSensorType, continuousEvent.ObservationSpecs[0].BuiltInSensorType); + Assert.AreEqual((int)BuiltInActuatorType.VectorActuator, continuousEvent.ActuatorInfos[0].BuiltInActuatorType); + Assert.AreNotEqual(null, continuousEvent.ModelHash); + + // Make sure nested fields get serialized + var jsonString = JsonUtility.ToJson(continuousEvent, true); + Assert.IsTrue(jsonString.Contains("ObservationSpecs")); + Assert.IsTrue(jsonString.Contains("ActionSpec")); + Assert.IsTrue(jsonString.Contains("NumDiscreteActions")); + Assert.IsTrue(jsonString.Contains("SensorName")); + Assert.IsTrue(jsonString.Contains("Flags")); + Assert.IsTrue(jsonString.Contains("ActuatorInfos")); + } + + [Test] + public void TestSentisPolicy() + { + // Explicitly request decisions for a policy so we get code coverage on the event sending + using (new AnalyticsUtils.DisableAnalyticsSending()) + { + var sensors = new List { sensor_21_20_3.Sensor, sensor_20_22_3.Sensor }; + var policy = new SentisPolicy( + GetContinuous2vis8vec2actionActionSpec(), + Array.Empty(), + continuousONNXModel, + InferenceDevice.Burst, + "testBehavior" + ); + policy.RequestDecision(new AgentInfo(), sensors); + } + Academy.Instance.Dispose(); + } + } +} diff --git a/com.unity.ml-agents.tests/Tests/Editor/Analytics/InferenceAnalyticsTests.cs.meta b/com.unity.ml-agents.tests/Tests/Editor/Analytics/InferenceAnalyticsTests.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..20f024f03b11183f33ac49c5ace67c5a0ca3ef31 Binary files /dev/null and b/com.unity.ml-agents.tests/Tests/Editor/Analytics/InferenceAnalyticsTests.cs.meta differ diff --git a/com.unity.ml-agents.tests/Tests/Editor/Analytics/TrainingAnalyticsTest.cs b/com.unity.ml-agents.tests/Tests/Editor/Analytics/TrainingAnalyticsTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..99a6622ca5409efac7d8fd98b5c8eadd85b8e4ae --- /dev/null +++ b/com.unity.ml-agents.tests/Tests/Editor/Analytics/TrainingAnalyticsTest.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using NUnit.Framework; +using Unity.MLAgents.Sensors; +using Unity.MLAgents.Actuators; +using Unity.MLAgents.Analytics; +using Unity.MLAgents.Policies; +using UnityEditor; + +namespace Unity.MLAgents.Tests.Analytics +{ + [TestFixture] + public class TrainingAnalyticsTests + { + [TestCase("foo?team=42", ExpectedResult = "foo")] + [TestCase("foo", ExpectedResult = "foo")] + [TestCase("foo?bar?team=1337", ExpectedResult = "foo?bar")] + public string TestParseBehaviorName(string fullyQualifiedBehaviorName) + { + return TrainingAnalytics.ParseBehaviorName(fullyQualifiedBehaviorName); + } + + [Test] + public void TestRemotePolicyEvent() + { + var behaviorName = "testBehavior"; + var sensor1 = new Test3DSensor("SensorA", 21, 20, 3); + var sensor2 = new Test3DSensor("SensorB", 20, 22, 3); + var sensors = new List { sensor1, sensor2 }; + + var actionSpec = ActionSpec.MakeContinuous(2); + + var vectorActuator = new VectorActuator(null, actionSpec, "test'"); + var actuators = new IActuator[] { vectorActuator }; + + var remotePolicyEvent = TrainingAnalytics.GetEventForRemotePolicy(behaviorName, sensors, actionSpec, actuators); + + // The behavior name should be hashed, not pass-through. + Assert.AreNotEqual(behaviorName, remotePolicyEvent.BehaviorName); + + Assert.AreEqual(2, remotePolicyEvent.ObservationSpecs.Count); + Assert.AreEqual(3, remotePolicyEvent.ObservationSpecs[0].DimensionInfos.Length); + Assert.AreEqual(20, remotePolicyEvent.ObservationSpecs[0].DimensionInfos[1].Size); + Assert.AreEqual(0, remotePolicyEvent.ObservationSpecs[0].ObservationType); + Assert.AreEqual("None", remotePolicyEvent.ObservationSpecs[0].CompressionType); + Assert.AreEqual(Test3DSensor.k_BuiltInSensorType, remotePolicyEvent.ObservationSpecs[0].BuiltInSensorType); + + Assert.AreEqual(2, remotePolicyEvent.ActionSpec.NumContinuousActions); + Assert.AreEqual(0, remotePolicyEvent.ActionSpec.NumDiscreteActions); + + Assert.AreEqual(2, remotePolicyEvent.ActuatorInfos[0].NumContinuousActions); + Assert.AreEqual(0, remotePolicyEvent.ActuatorInfos[0].NumDiscreteActions); + } + + [Test] + public void TestRemotePolicy() + { + if (Academy.IsInitialized) + { + Academy.Instance.Dispose(); + } + + using (new AnalyticsUtils.DisableAnalyticsSending()) + { + var actionSpec = ActionSpec.MakeContinuous(3); + var policy = new RemotePolicy(actionSpec, Array.Empty(), "TestBehavior?team=42"); + policy.RequestDecision(new AgentInfo(), new List()); + } + + Academy.Instance.Dispose(); + } + + [TestCase("a name we expect to hash", ExpectedResult = "d084a8b6da6a6a1c097cdc9ffea95e1546da4647352113ed77cbe7b4192e6d73")] + [TestCase("another_name", ExpectedResult = "0b74613c872e79aba11e06eda3538f2b646eb2b459e75087829ea500bd703d0b")] + [TestCase("0b74613c872e79aba11e06eda3538f2b646eb2b459e75087829ea500bd703d0b", ExpectedResult = "0b74613c872e79aba11e06eda3538f2b646eb2b459e75087829ea500bd703d0b")] + public string TestTrainingBehaviorInitialized(string stringToMaybeHash) + { + var tbiEvent = new TrainingBehaviorInitializedEvent(); + tbiEvent.BehaviorName = stringToMaybeHash; + tbiEvent.Config = "{}"; + + var sanitizedEvent = TrainingAnalytics.SanitizeTrainingBehaviorInitializedEvent(tbiEvent); + return sanitizedEvent.BehaviorName; + } + + [Test] + public void TestEnableAnalytics() + { +#if UNITY_EDITOR && MLA_UNITY_ANALYTICS_MODULE && ENABLE_CLOUD_SERVICES_ANALYTICS + Assert.IsTrue(TrainingAnalytics.EnableAnalytics()); +#else + Assert.IsFalse(TrainingAnalytics.EnableAnalytics()); +#endif + } + } +} diff --git a/com.unity.ml-agents.tests/Tests/Editor/Analytics/TrainingAnalyticsTest.cs.meta b/com.unity.ml-agents.tests/Tests/Editor/Analytics/TrainingAnalyticsTest.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..df394c157a66e0e2da47d64adb4cbb90d1424762 Binary files /dev/null and b/com.unity.ml-agents.tests/Tests/Editor/Analytics/TrainingAnalyticsTest.cs.meta differ diff --git a/com.unity.ml-agents.tests/Tests/Editor/Areas.meta b/com.unity.ml-agents.tests/Tests/Editor/Areas.meta new file mode 100644 index 0000000000000000000000000000000000000000..42901a0e6bbff460f671542f11db5ea7f7f4bdef Binary files /dev/null and b/com.unity.ml-agents.tests/Tests/Editor/Areas.meta differ diff --git a/com.unity.ml-agents.tests/Tests/Editor/Areas/TrainingAreaReplicatorTests.cs b/com.unity.ml-agents.tests/Tests/Editor/Areas/TrainingAreaReplicatorTests.cs new file mode 100644 index 0000000000000000000000000000000000000000..1daeb980ee980b8caecba0d75a894518a227c00a --- /dev/null +++ b/com.unity.ml-agents.tests/Tests/Editor/Areas/TrainingAreaReplicatorTests.cs @@ -0,0 +1,83 @@ +using System.Linq; +using NUnit.Framework; +using Unity.Mathematics; +using Unity.MLAgents.Areas; +using UnityEngine; + +namespace Unity.MLAgents.Tests.Areas +{ + [TestFixture] + public class TrainingAreaReplicatorTests + { + TrainingAreaReplicator m_Replicator; + + [SetUp] + public void Setup() + { + var gameObject = new GameObject(); + var trainingArea = new GameObject(); + trainingArea.name = "MyTrainingArea"; + m_Replicator = gameObject.AddComponent(); + m_Replicator.baseArea = trainingArea; + } + + [TearDown] + public void TearDown() + { + var trainingAreas = Resources.FindObjectsOfTypeAll().Where(obj => obj.name == m_Replicator.TrainingAreaName); + foreach (var trainingArea in trainingAreas) + { + Object.DestroyImmediate(trainingArea); + } + m_Replicator = null; + } + + private static object[] NumAreasCases = + { + new object[] {1}, + new object[] {2}, + new object[] {5}, + new object[] {7}, + new object[] {8}, + new object[] {64}, + new object[] {63}, + }; + + [TestCaseSource(nameof(NumAreasCases))] + public void TestComputeGridSize(int numAreas) + { + m_Replicator.numAreas = numAreas; + m_Replicator.Awake(); + m_Replicator.OnEnable(); + var m_CorrectGridSize = int3.zero; + var m_RootNumAreas = Mathf.Pow(numAreas, 1.0f / 3.0f); + m_CorrectGridSize.x = Mathf.CeilToInt(m_RootNumAreas); + m_CorrectGridSize.y = Mathf.CeilToInt(m_RootNumAreas); + m_CorrectGridSize.z = Mathf.CeilToInt((float)numAreas / (m_CorrectGridSize.x * m_CorrectGridSize.y)); + Assert.GreaterOrEqual(m_Replicator.GridSize.x * m_Replicator.GridSize.y * m_Replicator.GridSize.z, m_Replicator.numAreas); + Assert.AreEqual(m_CorrectGridSize, m_Replicator.GridSize); + } + + [Test] + public void TestAddEnvironments() + { + m_Replicator.numAreas = 10; + m_Replicator.buildOnly = false; + m_Replicator.Awake(); + m_Replicator.OnEnable(); + var trainingAreas = Resources.FindObjectsOfTypeAll().Where(obj => obj.name == m_Replicator.TrainingAreaName); + Assert.AreEqual(10, trainingAreas.Count()); + } + + [Test] + public void TestAddEnvironmentsBuildOnly() + { + m_Replicator.numAreas = 10; + m_Replicator.buildOnly = true; + m_Replicator.Awake(); + m_Replicator.OnEnable(); + var trainingAreas = Resources.FindObjectsOfTypeAll().Where(obj => obj.name == m_Replicator.TrainingAreaName); + Assert.AreEqual(1, trainingAreas.Count()); + } + } +} diff --git a/com.unity.ml-agents.tests/Tests/Editor/Areas/TrainingAreaReplicatorTests.cs.meta b/com.unity.ml-agents.tests/Tests/Editor/Areas/TrainingAreaReplicatorTests.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..4ebc4ba4d109cb07725de74739fdc04e210acc24 Binary files /dev/null and b/com.unity.ml-agents.tests/Tests/Editor/Areas/TrainingAreaReplicatorTests.cs.meta differ diff --git a/com.unity.ml-agents.tests/Tests/Editor/BehaviorParameterTests.cs b/com.unity.ml-agents.tests/Tests/Editor/BehaviorParameterTests.cs new file mode 100644 index 0000000000000000000000000000000000000000..11c09a894c40eb1b0295fa1ff4ea3335fe6b1fee --- /dev/null +++ b/com.unity.ml-agents.tests/Tests/Editor/BehaviorParameterTests.cs @@ -0,0 +1,76 @@ +using NUnit.Framework; +using Unity.InferenceEngine; +using Unity.MLAgents.Actuators; +using UnityEngine; +using Unity.MLAgents.Policies; +using UnityEditor; +using UnityEngine.TestTools; + +namespace Unity.MLAgents.Tests +{ + [TestFixture] + public class BehaviorParameterTests : IHeuristicProvider + { + const string k_continuousONNXPath = "Packages/com.unity.ml-agents.tests/Tests/Editor/TestModels/continuous2vis8vec2action_v1_0.onnx"; + public void Heuristic(in ActionBuffers actionsOut) + { + // No-op + } + + [Test] + public void TestNoModelInferenceOnlyThrows() + { + var gameObj = new GameObject(); + var bp = gameObj.AddComponent(); + bp.BehaviorType = BehaviorType.InferenceOnly; + var actionSpec = new ActionSpec(); + + Assert.Throws(() => + { + bp.GeneratePolicy(actionSpec, new ActuatorManager()); + }); + } + + [Test] + public void TestIsInHeuristicMode() + { + var gameObj = new GameObject(); + var bp = gameObj.AddComponent(); + bp.Model = null; + gameObj.AddComponent(); + bp.BehaviorType = BehaviorType.HeuristicOnly; + Assert.IsTrue(bp.IsInHeuristicMode()); + + bp.BehaviorType = BehaviorType.Default; + Assert.IsTrue(bp.IsInHeuristicMode()); + + bp.Model = ScriptableObject.CreateInstance(); + Assert.IsFalse(bp.IsInHeuristicMode()); + } + + [Test] + public void TestPolicyUpdateEventFired() + { + var gameObj = new GameObject(); + var bp = gameObj.AddComponent(); + gameObj.AddComponent().LazyInitialize(); + bp.OnPolicyUpdated += delegate (bool isInHeuristicMode) { Debug.Log($"OnPolicyChanged:{isInHeuristicMode}"); }; + bp.BehaviorType = BehaviorType.HeuristicOnly; + LogAssert.Expect(LogType.Log, $"OnPolicyChanged:{true}"); + + bp.BehaviorType = BehaviorType.Default; + LogAssert.Expect(LogType.Log, $"OnPolicyChanged:{true}"); + + Assert.Throws(() => + { + bp.BehaviorType = BehaviorType.InferenceOnly; + }); + + bp.Model = AssetDatabase.LoadAssetAtPath(k_continuousONNXPath); + LogAssert.Expect(LogType.Log, $"OnPolicyChanged:{false}"); + + bp.BehaviorType = BehaviorType.HeuristicOnly; + LogAssert.Expect(LogType.Log, $"OnPolicyChanged:{true}"); + } + } +} diff --git a/com.unity.ml-agents.tests/Tests/Editor/BehaviorParameterTests.cs.meta b/com.unity.ml-agents.tests/Tests/Editor/BehaviorParameterTests.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..0656104d8cba5cfe3fccfeca12b082d00c1c3bf2 Binary files /dev/null and b/com.unity.ml-agents.tests/Tests/Editor/BehaviorParameterTests.cs.meta differ diff --git a/com.unity.ml-agents.tests/Tests/Editor/Communicator.meta b/com.unity.ml-agents.tests/Tests/Editor/Communicator.meta new file mode 100644 index 0000000000000000000000000000000000000000..170d273a27c4c1ed34295947e03911dd1f1dbce1 Binary files /dev/null and b/com.unity.ml-agents.tests/Tests/Editor/Communicator.meta differ diff --git a/com.unity.ml-agents.tests/Tests/Editor/Communicator/GrpcExtensionsTests.cs b/com.unity.ml-agents.tests/Tests/Editor/Communicator/GrpcExtensionsTests.cs new file mode 100644 index 0000000000000000000000000000000000000000..14dc5fceae71b77a9fc21ba9ac15f9339f5723dc --- /dev/null +++ b/com.unity.ml-agents.tests/Tests/Editor/Communicator/GrpcExtensionsTests.cs @@ -0,0 +1,276 @@ +using System; +using System.Text.RegularExpressions; +using Google.Protobuf; +using NUnit.Framework; +using Unity.MLAgents.Actuators; +using Unity.MLAgents.Demonstrations; +using Unity.MLAgents.Policies; +using Unity.MLAgents.Sensors; + +using Unity.MLAgents.Analytics; +using Unity.MLAgents.CommunicatorObjects; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Unity.MLAgents.Tests +{ + [TestFixture] + public class GrpcExtensionsTests + { + [SetUp] + public void SetUp() + { + Academy.Instance.TrainerCapabilities = new UnityRLCapabilities(); + } + + [Test] + public void TestDefaultBrainParametersToProto() + { + // Should be able to convert a default instance to proto. + var brain = new BrainParameters(); + brain.ToProto("foo", false); + Academy.Instance.TrainerCapabilities = new UnityRLCapabilities + { + BaseRLCapabilities = true, + HybridActions = false + }; + brain.ToProto("foo", false); + } + + [Test] + public void TestDefaultActionSpecToProto() + { + // Should be able to convert a default instance to proto. + var actionSpec = new ActionSpec(); + actionSpec.ToBrainParametersProto("foo", false); + Academy.Instance.TrainerCapabilities = new UnityRLCapabilities + { + BaseRLCapabilities = true, + HybridActions = false + }; + actionSpec.ToBrainParametersProto("foo", false); + + Academy.Instance.TrainerCapabilities = new UnityRLCapabilities(); + // Continuous + actionSpec = ActionSpec.MakeContinuous(3); + actionSpec.ToBrainParametersProto("foo", false); + Academy.Instance.TrainerCapabilities = new UnityRLCapabilities + { + BaseRLCapabilities = true, + HybridActions = false + }; + actionSpec.ToBrainParametersProto("foo", false); + + Academy.Instance.TrainerCapabilities = new UnityRLCapabilities(); + + // Discrete + actionSpec = ActionSpec.MakeDiscrete(1, 2, 3); + actionSpec.ToBrainParametersProto("foo", false); + Academy.Instance.TrainerCapabilities = new UnityRLCapabilities + { + BaseRLCapabilities = true, + HybridActions = false + }; + actionSpec.ToBrainParametersProto("foo", false); + } + + [Test] + public void ToBrainParameters() + { + // Should be able to convert a default instance to proto. + var actionSpec = new ActionSpec(); + actionSpec.ToBrainParametersProto("foo", false).ToBrainParameters(); + Academy.Instance.TrainerCapabilities = new UnityRLCapabilities + { + BaseRLCapabilities = true, + HybridActions = false + }; + actionSpec.ToBrainParametersProto("foo", false).ToBrainParameters(); + + Academy.Instance.TrainerCapabilities = new UnityRLCapabilities(); + // Continuous + actionSpec = ActionSpec.MakeContinuous(3); + actionSpec.ToBrainParametersProto("foo", false).ToBrainParameters(); + Academy.Instance.TrainerCapabilities = new UnityRLCapabilities + { + BaseRLCapabilities = true, + HybridActions = false + }; + actionSpec.ToBrainParametersProto("foo", false).ToBrainParameters(); + + Academy.Instance.TrainerCapabilities = new UnityRLCapabilities(); + + // Discrete + actionSpec = ActionSpec.MakeDiscrete(1, 2, 3); + actionSpec.ToBrainParametersProto("foo", false).ToBrainParameters(); + Academy.Instance.TrainerCapabilities = new UnityRLCapabilities + { + BaseRLCapabilities = true, + HybridActions = false + }; + actionSpec.ToBrainParametersProto("foo", false).ToBrainParameters(); + } + + [Test] + public void TestDefaultAgentInfoToProto() + { + // Should be able to convert a default instance to proto. + var agentInfo = new AgentInfo(); + var pairProto = agentInfo.ToInfoActionPairProto(); + pairProto.AgentInfo.Observations.Add(new ObservationProto + { + CompressedData = ByteString.Empty, + CompressionType = CompressionTypeProto.None, + FloatData = new ObservationProto.Types.FloatData(), + ObservationType = ObservationTypeProto.Default, + Name = "Sensor" + }); + pairProto.AgentInfo.Observations[0].Shape.Add(0); + pairProto.GetObservationSummaries(); + agentInfo.ToAgentInfoProto(); + agentInfo.groupId = 1; + Academy.Instance.TrainerCapabilities = new UnityRLCapabilities + { + BaseRLCapabilities = true, + MultiAgentGroups = false + }; + agentInfo.ToAgentInfoProto(); + LogAssert.Expect(LogType.Warning, new Regex(".+")); + Academy.Instance.TrainerCapabilities = new UnityRLCapabilities + { + BaseRLCapabilities = true, + MultiAgentGroups = true + }; + agentInfo.ToAgentInfoProto(); + } + + [Test] + public void TestDefaultDemonstrationMetaDataToProto() + { + // Should be able to convert a default instance to proto. + var demoMetaData = new DemonstrationMetaData(); + demoMetaData.ToProto(); + } + + class DummySensor : ISensor + { + public ObservationSpec ObservationSpec; + public SensorCompressionType CompressionType; + + public ObservationSpec GetObservationSpec() + { + return ObservationSpec; + } + + public int Write(ObservationWriter writer) + { + return 0; + } + + public byte[] GetCompressedObservation() + { + return new byte[] { 13, 37 }; + } + + public void Update() { } + + public void Reset() { } + + public CompressionSpec GetCompressionSpec() + { + return new CompressionSpec(CompressionType); + } + + public string GetName() + { + return "Dummy"; + } + } + + [Test] + public void TestGetObservationProtoCapabilities() + { + // Shape, compression type, concatenatedPngObservations, expect throw + var variants = new[] + { + // Vector observations + (new[] {3}, SensorCompressionType.None, false, false), + // Uncompressed floats + (new[] {3, 4, 4}, SensorCompressionType.None, false, false), + // Compressed floats, 3 channels + (new[] {3, 4, 4}, SensorCompressionType.PNG, false, true), + + // Compressed floats, >3 channels + (new[] {4, 4, 4}, SensorCompressionType.PNG, false, false), // Unsupported - results in uncompressed + (new[] {4, 4, 4}, SensorCompressionType.PNG, true, true), // Supported compressed + }; + + foreach (var (shape, compressionType, supportsMultiPngObs, expectCompressed) in variants) + { + var inplaceShape = InplaceArray.FromList(shape); + var dummySensor = new DummySensor(); + var obsWriter = new ObservationWriter(); + + if (shape.Length == 1) + { + dummySensor.ObservationSpec = ObservationSpec.Vector(shape[0]); + } + else if (shape.Length == 3) + { + dummySensor.ObservationSpec = ObservationSpec.Visual(shape[0], shape[1], shape[2]); + } + else + { + throw new ArgumentOutOfRangeException(); + } + dummySensor.CompressionType = compressionType; + obsWriter.SetTarget(new float[128], inplaceShape, 0); + + var caps = new UnityRLCapabilities + { + ConcatenatedPngObservations = supportsMultiPngObs + }; + Academy.Instance.TrainerCapabilities = caps; + + + var obsProto = dummySensor.GetObservationProto(obsWriter); + if (expectCompressed) + { + Assert.Greater(obsProto.CompressedData.Length, 0); + Assert.AreEqual(obsProto.FloatData, null); + } + else + { + Assert.Greater(obsProto.FloatData.Data.Count, 0); + Assert.AreEqual(obsProto.CompressedData.Length, 0); + } + } + } + + [Test] + public void TestDefaultTrainingEvents() + { + var trainingEnvInit = new TrainingEnvironmentInitialized + { + PythonVersion = "test", + }; + var trainingEnvInitEvent = trainingEnvInit.ToTrainingEnvironmentInitializedEvent(); + Assert.AreEqual(trainingEnvInit.PythonVersion, trainingEnvInitEvent.TrainerPythonVersion); + + var trainingBehavInit = new TrainingBehaviorInitialized + { + BehaviorName = "testBehavior", + ExtrinsicRewardEnabled = true, + CuriosityRewardEnabled = true, + + RecurrentEnabled = true, + SelfPlayEnabled = true, + }; + var trainingBehavInitEvent = trainingBehavInit.ToTrainingBehaviorInitializedEvent(); + Assert.AreEqual(trainingBehavInit.BehaviorName, trainingBehavInitEvent.BehaviorName); + + Assert.AreEqual(RewardSignals.Extrinsic | RewardSignals.Curiosity, trainingBehavInitEvent.RewardSignalFlags); + Assert.AreEqual(TrainingFeatures.Recurrent | TrainingFeatures.SelfPlay, trainingBehavInitEvent.TrainingFeatureFlags); + } + } +} diff --git a/com.unity.ml-agents.tests/Tests/Editor/Communicator/GrpcExtensionsTests.cs.meta b/com.unity.ml-agents.tests/Tests/Editor/Communicator/GrpcExtensionsTests.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..411f1cd45ec165f8539030dbd355a5caabc54dad Binary files /dev/null and b/com.unity.ml-agents.tests/Tests/Editor/Communicator/GrpcExtensionsTests.cs.meta differ diff --git a/com.unity.ml-agents.tests/Tests/Editor/Communicator/RpcCommunicatorTests.cs b/com.unity.ml-agents.tests/Tests/Editor/Communicator/RpcCommunicatorTests.cs new file mode 100644 index 0000000000000000000000000000000000000000..937c8555fad4434504a21c565cfcdaa6831e4521 --- /dev/null +++ b/com.unity.ml-agents.tests/Tests/Editor/Communicator/RpcCommunicatorTests.cs @@ -0,0 +1,40 @@ +using NUnit.Framework; +using UnityEngine.TestTools; + +namespace Unity.MLAgents.Tests.Communicator +{ + [TestFixture] + public class RpcCommunicatorTests + { + [Test] + public void TestCheckCommunicationVersionsAreCompatible() + { + var unityVerStr = "1.0.0"; + var pythonVerStr = "1.0.0"; + + Assert.IsTrue(RpcCommunicator.CheckCommunicationVersionsAreCompatible(unityVerStr, + pythonVerStr)); + LogAssert.NoUnexpectedReceived(); + + pythonVerStr = "1.1.0"; + Assert.IsTrue(RpcCommunicator.CheckCommunicationVersionsAreCompatible(unityVerStr, + pythonVerStr)); + LogAssert.NoUnexpectedReceived(); + + unityVerStr = "2.0.0"; + Assert.IsFalse(RpcCommunicator.CheckCommunicationVersionsAreCompatible(unityVerStr, + pythonVerStr)); + + unityVerStr = "0.15.0"; + pythonVerStr = "0.15.0"; + Assert.IsTrue(RpcCommunicator.CheckCommunicationVersionsAreCompatible(unityVerStr, + pythonVerStr)); + unityVerStr = "0.16.0"; + Assert.IsFalse(RpcCommunicator.CheckCommunicationVersionsAreCompatible(unityVerStr, + pythonVerStr)); + unityVerStr = "1.15.0"; + Assert.IsFalse(RpcCommunicator.CheckCommunicationVersionsAreCompatible(unityVerStr, + pythonVerStr)); + } + } +} diff --git a/com.unity.ml-agents.tests/Tests/Editor/Communicator/RpcCommunicatorTests.cs.meta b/com.unity.ml-agents.tests/Tests/Editor/Communicator/RpcCommunicatorTests.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..1d0689e5cb7e83378b85c80616d0b15c264a6b8e Binary files /dev/null and b/com.unity.ml-agents.tests/Tests/Editor/Communicator/RpcCommunicatorTests.cs.meta differ diff --git a/com.unity.ml-agents.tests/Tests/Editor/Communicator/UnityRLCapabilitiesTests.cs b/com.unity.ml-agents.tests/Tests/Editor/Communicator/UnityRLCapabilitiesTests.cs new file mode 100644 index 0000000000000000000000000000000000000000..402ceda612f886b72f2be9bfcd2005d5b8e2d087 --- /dev/null +++ b/com.unity.ml-agents.tests/Tests/Editor/Communicator/UnityRLCapabilitiesTests.cs @@ -0,0 +1,22 @@ +using System.Text.RegularExpressions; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Unity.MLAgents.Tests.Communicator +{ + [TestFixture] + public class UnityRLCapabilitiesTests + { + [Test] + public void TestWarnOnPythonMissingBaseRLCapabilities() + { + var caps = new UnityRLCapabilities(); + Assert.False(caps.WarnOnPythonMissingBaseRLCapabilities()); + LogAssert.NoUnexpectedReceived(); + caps = new UnityRLCapabilities(false); + Assert.True(caps.WarnOnPythonMissingBaseRLCapabilities()); + LogAssert.Expect(LogType.Warning, new Regex(".+")); + } + } +} diff --git a/com.unity.ml-agents.tests/Tests/Editor/Communicator/UnityRLCapabilitiesTests.cs.meta b/com.unity.ml-agents.tests/Tests/Editor/Communicator/UnityRLCapabilitiesTests.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..fc7b8cec7ad9bd6ab590872c2376558e0147a54e Binary files /dev/null and b/com.unity.ml-agents.tests/Tests/Editor/Communicator/UnityRLCapabilitiesTests.cs.meta differ diff --git a/com.unity.ml-agents.tests/Tests/Editor/DemonstrationTests.cs b/com.unity.ml-agents.tests/Tests/Editor/DemonstrationTests.cs new file mode 100644 index 0000000000000000000000000000000000000000..55060f5645d20ea4befd058c884785d52b783729 --- /dev/null +++ b/com.unity.ml-agents.tests/Tests/Editor/DemonstrationTests.cs @@ -0,0 +1,150 @@ +using NUnit.Framework; +using UnityEngine; +using System.IO.Abstractions.TestingHelpers; +using System.Reflection; +using Unity.MLAgents.Actuators; +using Unity.MLAgents.CommunicatorObjects; +using Unity.MLAgents.Sensors; +using Unity.MLAgents.Demonstrations; +using Unity.MLAgents.Policies; +using Unity.MLAgents.Utils.Tests; + +namespace Unity.MLAgents.Tests +{ + [TestFixture] + public class DemonstrationTests + { + const string k_DemoDirectory = "Assets/Demonstrations/"; + const string k_ExtensionType = ".demo"; + const string k_DemoName = "Test"; + + [SetUp] + public void SetUp() + { + if (Academy.IsInitialized) + { + Academy.Instance.Dispose(); + } + } + + [Test] + public void TestSanitization() + { + const string dirtyString = "abc1234567&!@"; + const string knownCleanString = "abc123"; + var cleanString = DemonstrationRecorder.SanitizeName(dirtyString, 6); + Assert.AreNotEqual(dirtyString, cleanString); + Assert.AreEqual(cleanString, knownCleanString); + } + + [Test] + public void TestStoreInitialize() + { + var fileSystem = new MockFileSystem(); + + var gameobj = new GameObject("gameObj"); + + var bp = gameobj.AddComponent(); + bp.BrainParameters.VectorObservationSize = 3; + bp.BrainParameters.NumStackedVectorObservations = 2; + bp.BrainParameters.VectorActionDescriptions = new[] { "TestActionA", "TestActionB" }; + bp.BrainParameters.ActionSpec = ActionSpec.MakeDiscrete(2, 2); + + gameobj.AddComponent(); + + Assert.IsFalse(fileSystem.Directory.Exists(k_DemoDirectory)); + + var demoRec = gameobj.AddComponent(); + demoRec.Record = true; + demoRec.DemonstrationName = k_DemoName; + demoRec.DemonstrationDirectory = k_DemoDirectory; + var demoWriter = demoRec.LazyInitialize(fileSystem); + + Assert.IsTrue(fileSystem.Directory.Exists(k_DemoDirectory)); + Assert.IsTrue(fileSystem.FileExists(k_DemoDirectory + k_DemoName + k_ExtensionType)); + + var agentInfo = new AgentInfo + { + reward = 1f, + discreteActionMasks = new[] { false, true }, + done = true, + episodeId = 5, + maxStepReached = true, + storedActions = new ActionBuffers(null, new[] { 0, 1 }), + }; + + + demoWriter.Record(agentInfo, new System.Collections.Generic.List()); + demoRec.Close(); + + // Make sure close can be called multiple times + demoWriter.Close(); + demoRec.Close(); + + // Make sure trying to write after closing doesn't raise an error. + demoWriter.Record(agentInfo, new System.Collections.Generic.List()); + } + + public class ObservationAgent : TestAgent + { + public override void CollectObservations(VectorSensor sensor) + { + collectObservationsCalls += 1; + sensor.AddObservation(1f); + sensor.AddObservation(2f); + sensor.AddObservation(3f); + } + } + + [Test] + public void TestAgentWrite() + { + var agentGo1 = new GameObject("TestAgent"); + var bpA = agentGo1.AddComponent(); + bpA.BrainParameters.VectorObservationSize = 3; + bpA.BrainParameters.NumStackedVectorObservations = 1; + bpA.BrainParameters.VectorActionDescriptions = new[] { "TestActionA", "TestActionB" }; + bpA.BrainParameters.ActionSpec = ActionSpec.MakeDiscrete(2, 2); + + agentGo1.AddComponent(); + var agent1 = agentGo1.GetComponent(); + + agentGo1.AddComponent(); + var demoRecorder = agentGo1.GetComponent(); + var fileSystem = new MockFileSystem(); + demoRecorder.DemonstrationDirectory = k_DemoDirectory; + demoRecorder.DemonstrationName = "TestBrain"; + demoRecorder.Record = true; + demoRecorder.LazyInitialize(fileSystem); + + var agentEnableMethod = typeof(Agent).GetMethod("OnEnable", + BindingFlags.Instance | BindingFlags.NonPublic); + var agentSendInfo = typeof(Agent).GetMethod("SendInfo", + BindingFlags.Instance | BindingFlags.NonPublic); + + agentEnableMethod?.Invoke(agent1, new object[] { }); + + // Step the agent + agent1.RequestDecision(); + agentSendInfo?.Invoke(agent1, new object[] { }); + + demoRecorder.Close(); + + // Read back the demo file and make sure observations were written + var reader = fileSystem.File.OpenRead("Assets/Demonstrations/TestBrain.demo"); + reader.Seek(DemonstrationWriter.MetaDataBytes + 1, 0); + BrainParametersProto.Parser.ParseDelimitedFrom(reader); + + var agentInfoProto = AgentInfoActionPairProto.Parser.ParseDelimitedFrom(reader).AgentInfo; + var obs = agentInfoProto.Observations[2]; // skip dummy sensors + { + var vecObs = obs.FloatData.Data; + Assert.AreEqual(bpA.BrainParameters.VectorObservationSize, vecObs.Count); + for (var i = 0; i < vecObs.Count; i++) + { + Assert.AreEqual((float)i + 1, vecObs[i]); + } + } + } + } +} diff --git a/com.unity.ml-agents.tests/Tests/Editor/DemonstrationTests.cs.meta b/com.unity.ml-agents.tests/Tests/Editor/DemonstrationTests.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..434861ff9b9625cff65d36e94a54d5c49d89bfa5 Binary files /dev/null and b/com.unity.ml-agents.tests/Tests/Editor/DemonstrationTests.cs.meta differ diff --git a/com.unity.ml-agents.tests/Tests/Editor/Inference.meta b/com.unity.ml-agents.tests/Tests/Editor/Inference.meta new file mode 100644 index 0000000000000000000000000000000000000000..1427471ab066360cb0a8226fb0a4fe50749bab5c Binary files /dev/null and b/com.unity.ml-agents.tests/Tests/Editor/Inference.meta differ diff --git a/com.unity.ml-agents.tests/Tests/Editor/Inference/DiscreteActionOutputApplierTest.cs b/com.unity.ml-agents.tests/Tests/Editor/Inference/DiscreteActionOutputApplierTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..9cfbd8b3be7030116ef52473d3dd0db1b824c73d --- /dev/null +++ b/com.unity.ml-agents.tests/Tests/Editor/Inference/DiscreteActionOutputApplierTest.cs @@ -0,0 +1,83 @@ +using System.Collections.Generic; +using Unity.InferenceEngine; +using NUnit.Framework; +using Unity.MLAgents.Actuators; +using Unity.MLAgents.Inference; + +namespace Unity.MLAgents.Tests +{ + public class DiscreteActionOutputApplierTest + { + [Test] + public void TestDiscreteApply() + { + var actionSpec = ActionSpec.MakeDiscrete(3, 2); + + var applier = new DiscreteActionOutputApplier(actionSpec, 2020); + var agentIds = new List { 42, 1337 }; + var actionBuffers = new Dictionary(); + actionBuffers[42] = new ActionBuffers(actionSpec); + actionBuffers[1337] = new ActionBuffers(actionSpec); + + var actionTensor = new TensorProxy + { + data = new Tensor( + new TensorShape(2, 2), + new[] + { + 2, // Agent 0, branch 0 + 1, // Agent 0, branch 1 + 0, // Agent 1, branch 0 + 0 // Agent 1, branch 1 + }), + shape = new int[] { 2, 2 }, + valueType = TensorProxy.TensorType.Integer + }; + + applier.Apply(actionTensor, agentIds, actionBuffers); + Assert.AreEqual(2, actionBuffers[42].DiscreteActions[0]); + Assert.AreEqual(1, actionBuffers[42].DiscreteActions[1]); + + Assert.AreEqual(0, actionBuffers[1337].DiscreteActions[0]); + Assert.AreEqual(0, actionBuffers[1337].DiscreteActions[1]); + } + } + + public class LegacyDiscreteActionOutputApplierTest + { + [Test] + public void TestDiscreteApply() + { + var actionSpec = ActionSpec.MakeDiscrete(3, 2); + const float smallLogProb = -1000.0f; + const float largeLogProb = -1.0f; + + var logProbs = new TensorProxy + { + data = new Tensor( + new TensorShape(2, 5), + new[] + { + smallLogProb, smallLogProb, largeLogProb, // Agent 0, branch 0 + smallLogProb, largeLogProb, // Agent 0, branch 1 + largeLogProb, smallLogProb, smallLogProb, // Agent 1, branch 0 + largeLogProb, smallLogProb, // Agent 1, branch 1 + }), + valueType = TensorProxy.TensorType.FloatingPoint + }; + + var applier = new LegacyDiscreteActionOutputApplier(actionSpec, 2020); + var agentIds = new List { 42, 1337 }; + var actionBuffers = new Dictionary(); + actionBuffers[42] = new ActionBuffers(actionSpec); + actionBuffers[1337] = new ActionBuffers(actionSpec); + + applier.Apply(logProbs, agentIds, actionBuffers); + Assert.AreEqual(2, actionBuffers[42].DiscreteActions[0]); + Assert.AreEqual(1, actionBuffers[42].DiscreteActions[1]); + + Assert.AreEqual(0, actionBuffers[1337].DiscreteActions[0]); + Assert.AreEqual(0, actionBuffers[1337].DiscreteActions[1]); + } + } +} diff --git a/com.unity.ml-agents.tests/Tests/Editor/Inference/DiscreteActionOutputApplierTest.cs.meta b/com.unity.ml-agents.tests/Tests/Editor/Inference/DiscreteActionOutputApplierTest.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..ac93a50aaba54c1924c30d7c96b2e53b58e226f5 Binary files /dev/null and b/com.unity.ml-agents.tests/Tests/Editor/Inference/DiscreteActionOutputApplierTest.cs.meta differ diff --git a/com.unity.ml-agents.tests/Tests/Editor/Inference/EditModeTestInternalBrainTensorApplier.cs b/com.unity.ml-agents.tests/Tests/Editor/Inference/EditModeTestInternalBrainTensorApplier.cs new file mode 100644 index 0000000000000000000000000000000000000000..3c6a8de1674b960aaa678256e64f019a75869ba9 --- /dev/null +++ b/com.unity.ml-agents.tests/Tests/Editor/Inference/EditModeTestInternalBrainTensorApplier.cs @@ -0,0 +1,192 @@ +using System.Collections.Generic; +using NUnit.Framework; +using Unity.InferenceEngine; +using Unity.MLAgents.Actuators; +using Unity.MLAgents.Inference; + +namespace Unity.MLAgents.Tests +{ + public class EditModeTestInternalBrainTensorApplier + { + class TestAgent : Agent { } + + [Test] + public void Construction() + { + var actionSpec = new ActionSpec(); + var mem = new Dictionary>(); + var tensorGenerator = new TensorApplier(actionSpec, 0, mem); + Assert.IsNotNull(tensorGenerator); + } + + [Test] + public void ApplyContinuousActionOutput() + { + var actionSpec = ActionSpec.MakeContinuous(3); + var inputTensor = new TensorProxy() + { + shape = new int[] { 2, 3 }, + data = new Tensor(new TensorShape(2, 3), new float[] { 1, 2, 3, 4, 5, 6 }) + }; + + var applier = new ContinuousActionOutputApplier(actionSpec); + + var agentIds = new List() { 0, 1 }; + + // Dictionary from AgentId to Action + var actionDict = new Dictionary() { { 0, ActionBuffers.Empty }, { 1, ActionBuffers.Empty } }; + + applier.Apply(inputTensor, agentIds, actionDict); + + + Assert.AreEqual(actionDict[0].ContinuousActions[0], 1); + Assert.AreEqual(actionDict[0].ContinuousActions[1], 2); + Assert.AreEqual(actionDict[0].ContinuousActions[2], 3); + + Assert.AreEqual(actionDict[1].ContinuousActions[0], 4); + Assert.AreEqual(actionDict[1].ContinuousActions[1], 5); + Assert.AreEqual(actionDict[1].ContinuousActions[2], 6); + } + + [Test] + public void ApplyDiscreteActionOutputLegacy() + { + var actionSpec = ActionSpec.MakeDiscrete(2, 3); + var inputTensor = new TensorProxy() + { + shape = new int[] { 2, 5 }, + data = new Tensor( + new TensorShape(2, 5), + new[] { 0.5f, 22.5f, 0.1f, 5f, 1f, 4f, 5f, 6f, 7f, 8f }) + }; + var applier = new LegacyDiscreteActionOutputApplier(actionSpec, 0); + + var agentIds = new List() { 0, 1 }; + + // Dictionary from AgentId to Action + var actionDict = new Dictionary() { { 0, ActionBuffers.Empty }, { 1, ActionBuffers.Empty } }; + + + applier.Apply(inputTensor, agentIds, actionDict); + + Assert.AreEqual(actionDict[0].DiscreteActions[0], 1); + Assert.AreEqual(actionDict[0].DiscreteActions[1], 1); + + Assert.AreEqual(actionDict[1].DiscreteActions[0], 1); + Assert.AreEqual(actionDict[1].DiscreteActions[1], 2); + } + + [Test] + public void ApplyDiscreteActionOutput() + { + var actionSpec = ActionSpec.MakeDiscrete(2, 3); + var inputTensor = new TensorProxy() + { + shape = new int[] { 2, 2 }, + data = new Tensor( + new TensorShape(2, 2), + new[] { 1, 1, 1, 2 }), + valueType = TensorProxy.TensorType.Integer + }; + var applier = new DiscreteActionOutputApplier(actionSpec, 0); + + var agentIds = new List() { 0, 1 }; + + // Dictionary from AgentId to Action + var actionDict = new Dictionary() { { 0, ActionBuffers.Empty }, { 1, ActionBuffers.Empty } }; + + + applier.Apply(inputTensor, agentIds, actionDict); + + Assert.AreEqual(actionDict[0].DiscreteActions[0], 1); + Assert.AreEqual(actionDict[0].DiscreteActions[1], 1); + + Assert.AreEqual(actionDict[1].DiscreteActions[0], 1); + Assert.AreEqual(actionDict[1].DiscreteActions[1], 2); + } + + [Test] + public void ApplyHybridActionOutputLegacy() + { + var actionSpec = new ActionSpec(3, new[] { 2, 3 }); + var continuousInputTensor = new TensorProxy() + { + shape = new int[] { 2, 3 }, + data = new Tensor(new TensorShape(2, 3), new float[] { 1, 2, 3, 4, 5, 6 }) + }; + var discreteInputTensor = new TensorProxy() + { + shape = new int[] { 2, 8 }, + data = new Tensor( + new TensorShape(2, 5), + new[] { 0.5f, 22.5f, 0.1f, 5f, 1f, 4f, 5f, 6f, 7f, 8f }) + }; + var continuousApplier = new ContinuousActionOutputApplier(actionSpec); + var discreteApplier = new LegacyDiscreteActionOutputApplier(actionSpec, 0); + + var agentIds = new List() { 0, 1 }; + + // Dictionary from AgentId to Action + var actionDict = new Dictionary() { { 0, ActionBuffers.Empty }, { 1, ActionBuffers.Empty } }; + + + continuousApplier.Apply(continuousInputTensor, agentIds, actionDict); + discreteApplier.Apply(discreteInputTensor, agentIds, actionDict); + + Assert.AreEqual(actionDict[0].ContinuousActions[0], 1); + Assert.AreEqual(actionDict[0].ContinuousActions[1], 2); + Assert.AreEqual(actionDict[0].ContinuousActions[2], 3); + Assert.AreEqual(actionDict[0].DiscreteActions[0], 1); + Assert.AreEqual(actionDict[0].DiscreteActions[1], 1); + + Assert.AreEqual(actionDict[1].ContinuousActions[0], 4); + Assert.AreEqual(actionDict[1].ContinuousActions[1], 5); + Assert.AreEqual(actionDict[1].ContinuousActions[2], 6); + Assert.AreEqual(actionDict[1].DiscreteActions[0], 1); + Assert.AreEqual(actionDict[1].DiscreteActions[1], 2); + } + + [Test] + public void ApplyHybridActionOutput() + { + var actionSpec = new ActionSpec(3, new[] { 2, 3 }); + var continuousInputTensor = new TensorProxy() + { + shape = new int[] { 2, 3 }, + data = new Tensor(new TensorShape(2, 3), new float[] { 1, 2, 3, 4, 5, 6 }), + valueType = TensorProxy.TensorType.FloatingPoint + }; + var discreteInputTensor = new TensorProxy() + { + shape = new int[] { 2, 2 }, + data = new Tensor( + new TensorShape(2, 2), + new[] { 1, 1, 1, 2 }), + valueType = TensorProxy.TensorType.Integer + }; + var continuousApplier = new ContinuousActionOutputApplier(actionSpec); + var discreteApplier = new DiscreteActionOutputApplier(actionSpec, 0); + + var agentIds = new List() { 0, 1 }; + + // Dictionary from AgentId to Action + var actionDict = new Dictionary() { { 0, ActionBuffers.Empty }, { 1, ActionBuffers.Empty } }; + + + continuousApplier.Apply(continuousInputTensor, agentIds, actionDict); + discreteApplier.Apply(discreteInputTensor, agentIds, actionDict); + + Assert.AreEqual(actionDict[0].ContinuousActions[0], 1); + Assert.AreEqual(actionDict[0].ContinuousActions[1], 2); + Assert.AreEqual(actionDict[0].ContinuousActions[2], 3); + Assert.AreEqual(actionDict[0].DiscreteActions[0], 1); + Assert.AreEqual(actionDict[0].DiscreteActions[1], 1); + + Assert.AreEqual(actionDict[1].ContinuousActions[0], 4); + Assert.AreEqual(actionDict[1].ContinuousActions[1], 5); + Assert.AreEqual(actionDict[1].ContinuousActions[2], 6); + Assert.AreEqual(actionDict[1].DiscreteActions[0], 1); + Assert.AreEqual(actionDict[1].DiscreteActions[1], 2); + } + } +} diff --git a/com.unity.ml-agents.tests/Tests/Editor/Inference/EditModeTestInternalBrainTensorApplier.cs.meta b/com.unity.ml-agents.tests/Tests/Editor/Inference/EditModeTestInternalBrainTensorApplier.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..98212413d0769188f584596b3e06f1cc3da15480 Binary files /dev/null and b/com.unity.ml-agents.tests/Tests/Editor/Inference/EditModeTestInternalBrainTensorApplier.cs.meta differ diff --git a/com.unity.ml-agents.tests/Tests/Editor/Inference/EditModeTestInternalBrainTensorGenerator.cs b/com.unity.ml-agents.tests/Tests/Editor/Inference/EditModeTestInternalBrainTensorGenerator.cs new file mode 100644 index 0000000000000000000000000000000000000000..a5aed708ba12c812c982f3bedad74d5c22cc0c9c --- /dev/null +++ b/com.unity.ml-agents.tests/Tests/Editor/Inference/EditModeTestInternalBrainTensorGenerator.cs @@ -0,0 +1,313 @@ +using System.Collections.Generic; +using Unity.InferenceEngine; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.TestTools; +using Unity.MLAgents.Actuators; +using Unity.MLAgents.Inference; +using Unity.MLAgents.Policies; +using Unity.MLAgents.Sensors; +using Unity.MLAgents.Utils.Tests; + +namespace Unity.MLAgents.Tests +{ + internal class OverflowSensor : ISensor + { + readonly string m_Name; + readonly int m_Size; + + public OverflowSensor(string name, int size) + { + m_Name = name; + m_Size = size; + } + + public ObservationSpec GetObservationSpec() + { + return ObservationSpec.Vector(m_Size); + } + + public int Write(ObservationWriter writer) + { + for (var i = 0; i < m_Size; i++) + writer[i] = i + 1f; + return m_Size; + } + + public byte[] GetCompressedObservation() { return null; } + public CompressionSpec GetCompressionSpec() { return new CompressionSpec(SensorCompressionType.None); } + public string GetName() { return m_Name; } + public void Update() { } + public void Reset() { } + } + + [TestFixture] + public class EditModeTestInternalBrainTensorGenerator + { + [SetUp] + public void SetUp() + { + if (Academy.IsInitialized) + { + Academy.Instance.Dispose(); + } + } + + static List GetFakeAgents(ObservableAttributeOptions observableAttributeOptions = ObservableAttributeOptions.Ignore) + { + var goA = new GameObject("goA"); + var bpA = goA.AddComponent(); + bpA.BrainParameters.VectorObservationSize = 3; + bpA.BrainParameters.NumStackedVectorObservations = 1; + bpA.ObservableAttributeHandling = observableAttributeOptions; + var agentA = goA.AddComponent(); + + var goB = new GameObject("goB"); + var bpB = goB.AddComponent(); + bpB.BrainParameters.VectorObservationSize = 3; + bpB.BrainParameters.NumStackedVectorObservations = 1; + bpB.ObservableAttributeHandling = observableAttributeOptions; + var agentB = goB.AddComponent(); + + var agents = new List { agentA, agentB }; + foreach (var agent in agents) + { + agent.LazyInitialize(); + } + agentA.collectObservationsSensor.AddObservation(new Vector3(1, 2, 3)); + agentB.collectObservationsSensor.AddObservation(new Vector3(4, 5, 6)); + + var infoA = new AgentInfo + { + storedActions = new ActionBuffers(null, new[] { 1, 2 }), + discreteActionMasks = null, + }; + + var infoB = new AgentInfo + { + storedActions = new ActionBuffers(null, new[] { 3, 4 }), + discreteActionMasks = new[] { true, false, false, false, false }, + }; + + + agentA._Info = infoA; + agentB._Info = infoB; + return agents; + } + + [Test] + public void Construction() + { + var mem = new Dictionary>(); + var tensorGenerator = new TensorGenerator(0, mem); + Assert.IsNotNull(tensorGenerator); + } + + [Test] + public void GenerateBatchSize() + { + var inputTensor = new TensorProxy(); + const int batchSize = 4; + var generator = new BatchSizeGenerator(); + generator.Generate(inputTensor, batchSize, null); + Assert.IsNotNull(inputTensor.data); + Assert.AreEqual(((Tensor)inputTensor.data)[0], batchSize); + } + + [Test] + public void GenerateSequenceLength() + { + var inputTensor = new TensorProxy(); + const int batchSize = 4; + var generator = new SequenceLengthGenerator(); + generator.Generate(inputTensor, batchSize, null); + Assert.IsNotNull(inputTensor.data); + Assert.AreEqual(((Tensor)inputTensor.data)[0], 1); + } + + [Test] + public void GenerateVectorObservation() + { + var inputTensor = new TensorProxy + { + valueType = TensorProxy.TensorType.FloatingPoint, + shape = new int[] { 2, 4 } + }; + const int batchSize = 4; + var agentInfos = GetFakeAgents(ObservableAttributeOptions.ExamineAll); + var generator = new ObservationGenerator(); + generator.AddSensorIndex(0); // ObservableAttribute (size 1) + generator.AddSensorIndex(1); // TestSensor (size 0) + generator.AddSensorIndex(2); // TestSensor (size 0) + generator.AddSensorIndex(3); // VectorSensor (size 3) + var agent0 = agentInfos[0]; + var agent1 = agentInfos[1]; + var inputs = new List + { + new AgentInfoSensorsPair { agentInfo = agent0._Info, sensors = agent0.sensors }, + new AgentInfoSensorsPair { agentInfo = agent1._Info, sensors = agent1.sensors }, + }; + generator.Generate(inputTensor, batchSize, inputs); + Assert.IsNotNull(inputTensor.data); + Assert.AreEqual((int)((Tensor)inputTensor.data)[0, 1], 1); + Assert.AreEqual((int)((Tensor)inputTensor.data)[0, 3], 3); + Assert.AreEqual((int)((Tensor)inputTensor.data)[1, 1], 4); + Assert.AreEqual((int)((Tensor)inputTensor.data)[1, 3], 6); + } + + [Test] + public void GeneratePreviousActionInput() + { + var inputTensor = new TensorProxy + { + shape = new int[] { 2, 2 }, + valueType = TensorProxy.TensorType.Integer + }; + const int batchSize = 4; + var agentInfos = GetFakeAgents(); + var generator = new PreviousActionInputGenerator(); + var agent0 = agentInfos[0]; + var agent1 = agentInfos[1]; + var inputs = new List + { + new AgentInfoSensorsPair { agentInfo = agent0._Info, sensors = agent0.sensors }, + new AgentInfoSensorsPair { agentInfo = agent1._Info, sensors = agent1.sensors }, + }; + generator.Generate(inputTensor, batchSize, inputs); + Assert.IsNotNull(inputTensor.data); + Assert.AreEqual(((Tensor)inputTensor.data)[0, 0], 1); + Assert.AreEqual(((Tensor)inputTensor.data)[0, 1], 2); + Assert.AreEqual(((Tensor)inputTensor.data)[1, 0], 3); + Assert.AreEqual(((Tensor)inputTensor.data)[1, 1], 4); + } + + [Test] + public void GenerateActionMaskInput() + { + var inputTensor = new TensorProxy + { + shape = new int[] { 2, 5 }, + valueType = TensorProxy.TensorType.FloatingPoint + }; + const int batchSize = 4; + var agentInfos = GetFakeAgents(); + var generator = new ActionMaskInputGenerator(); + + var agent0 = agentInfos[0]; + var agent1 = agentInfos[1]; + var inputs = new List + { + new AgentInfoSensorsPair { agentInfo = agent0._Info, sensors = agent0.sensors }, + new AgentInfoSensorsPair { agentInfo = agent1._Info, sensors = agent1.sensors }, + }; + + generator.Generate(inputTensor, batchSize, inputs); + Assert.IsNotNull(inputTensor.data); + Assert.AreEqual((int)((Tensor)inputTensor.data)[0, 0], 1); + Assert.AreEqual((int)((Tensor)inputTensor.data)[0, 4], 1); + Assert.AreEqual((int)((Tensor)inputTensor.data)[1, 0], 0); + Assert.AreEqual((int)((Tensor)inputTensor.data)[1, 4], 1); + } + + [Test] + public void GenerateVectorObservation_CapacityGuardPreventsOverflow() + { + // Tensor can hold 3 floats, sensor0 fills it exactly (3), + // so the guard fires before sensor1 can write anything. + var inputTensor = new TensorProxy + { + valueType = TensorProxy.TensorType.FloatingPoint, + shape = new int[] { 1, 3 } + }; + + var sensor0 = new OverflowSensor("sensor0", 3); + var sensor1 = new OverflowSensor("sensor1", 3); + var sensors = new List { sensor0, sensor1 }; + + var generator = new ObservationGenerator(); + generator.AddSensorIndex(0); + generator.AddSensorIndex(1); + + var inputs = new List + { + new AgentInfoSensorsPair + { + agentInfo = new AgentInfo { done = false }, + sensors = sensors + } + }; + + LogAssert.Expect(LogType.Warning, new System.Text.RegularExpressions.Regex("Sensor write overflow")); + generator.Generate(inputTensor, 1, inputs); + + // First sensor's 3 writes land, second sensor is skipped by capacity guard + Assert.AreEqual(1f, ((Tensor)inputTensor.data)[0, 0]); + Assert.AreEqual(2f, ((Tensor)inputTensor.data)[0, 1]); + Assert.AreEqual(3f, ((Tensor)inputTensor.data)[0, 2]); + } + + [Test] + public void GenerateVectorObservation_SingleSensorOverflowIsClamped() + { + // Tensor can hold 2 floats, but sensor writes 5 + var inputTensor = new TensorProxy + { + valueType = TensorProxy.TensorType.FloatingPoint, + shape = new int[] { 1, 2 } + }; + + var sensor = new OverflowSensor("big_sensor", 5); + var sensors = new List { sensor }; + + var generator = new ObservationGenerator(); + generator.AddSensorIndex(0); + + var inputs = new List + { + new AgentInfoSensorsPair + { + agentInfo = new AgentInfo { done = false }, + sensors = sensors + } + }; + + // No crash — ObservationWriter bounds cap prevents the buffer overrun + generator.Generate(inputTensor, 1, inputs); + + Assert.AreEqual(1f, ((Tensor)inputTensor.data)[0, 0]); + Assert.AreEqual(2f, ((Tensor)inputTensor.data)[0, 1]); + } + + [Test] + public void GenerateVectorObservation_ExactFitNoWarning() + { + // Tensor exactly fits the sensor output — no warning should fire + var inputTensor = new TensorProxy + { + valueType = TensorProxy.TensorType.FloatingPoint, + shape = new int[] { 1, 3 } + }; + + var sensor = new OverflowSensor("exact_sensor", 3); + var sensors = new List { sensor }; + + var generator = new ObservationGenerator(); + generator.AddSensorIndex(0); + + var inputs = new List + { + new AgentInfoSensorsPair + { + agentInfo = new AgentInfo { done = false }, + sensors = sensors + } + }; + + generator.Generate(inputTensor, 1, inputs); + + Assert.AreEqual(1f, ((Tensor)inputTensor.data)[0, 0]); + Assert.AreEqual(2f, ((Tensor)inputTensor.data)[0, 1]); + Assert.AreEqual(3f, ((Tensor)inputTensor.data)[0, 2]); + } + } +} diff --git a/com.unity.ml-agents.tests/Tests/Editor/Inference/EditModeTestInternalBrainTensorGenerator.cs.meta b/com.unity.ml-agents.tests/Tests/Editor/Inference/EditModeTestInternalBrainTensorGenerator.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..bffd9e8144840dc85b279cf330a1f4bd3260cea4 Binary files /dev/null and b/com.unity.ml-agents.tests/Tests/Editor/Inference/EditModeTestInternalBrainTensorGenerator.cs.meta differ diff --git a/com.unity.ml-agents.tests/Tests/Editor/Inference/ModelRunnerTest.cs b/com.unity.ml-agents.tests/Tests/Editor/Inference/ModelRunnerTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..1a47e04ff4a9035bcf865a7a849c137771bb1d91 --- /dev/null +++ b/com.unity.ml-agents.tests/Tests/Editor/Inference/ModelRunnerTest.cs @@ -0,0 +1,240 @@ +using System; +using System.Linq; +using NUnit.Framework; +using UnityEngine; +using UnityEditor; +using Unity.InferenceEngine; +using Unity.MLAgents.Actuators; +using Unity.MLAgents.Inference; +using Unity.MLAgents.Policies; +using System.Collections.Generic; + +namespace Unity.MLAgents.Tests +{ + public class FloatThresholdComparer : IEqualityComparer + { + private readonly float _threshold; + public FloatThresholdComparer(float threshold) + { + _threshold = threshold; + } + + public bool Equals(float x, float y) + { + return Math.Abs(x - y) < _threshold; + } + + public int GetHashCode(float f) + { + throw new NotImplementedException("Unable to generate a hash code for threshold floats, do not use this method"); + } + } + + [TestFixture] + public class ModelRunnerTest + { + const string k_hybrid_ONNX_recurr_v2 = "Packages/com.unity.ml-agents.tests/Tests/Editor/TestModels/hybrid0vis8vec_2c_2_3d_v2_0.onnx"; + + const string k_continuousONNXPath = "Packages/com.unity.ml-agents.tests/Tests/Editor/TestModels/continuous2vis8vec2action_v1_0.onnx"; + const string k_discreteONNXPath = "Packages/com.unity.ml-agents.tests/Tests/Editor/TestModels/discrete1vis0vec_2_3action_obsolete_recurr_v1_0.onnx"; + const string k_hybridONNXPath = "Packages/com.unity.ml-agents.tests/Tests/Editor/TestModels/hybrid0vis53vec_3c_2daction_v1_0.onnx"; + // const string k_continuousNNPath = "Packages/com.unity.ml-agents.tests/Tests/Editor/TestModels/continuous2vis8vec2action_deprecated_v1_0.nn"; + // const string k_discreteNNPath = "Packages/com.unity.ml-agents.tests/Tests/Editor/TestModels/discrete1vis0vec_2_3action_recurr_deprecated_v1_0.nn"; + // models with deterministic action tensors + private const string k_deterministic_discreteNNPath = "Packages/com.unity.ml-agents.tests/Tests/Editor/TestModels/deterDiscrete1obs3action_v2_0.onnx"; + private const string k_deterministic_continuousNNPath = "Packages/com.unity.ml-agents.tests/Tests/Editor/TestModels/deterContinuous2vis8vec2action_v2_0.onnx"; + + ModelAsset hybridONNXModelV2; + ModelAsset continuousONNXModel; + ModelAsset discreteONNXModel; + ModelAsset hybridONNXModel; + // Model continuousNNModel; + // Model discreteNNModel; + ModelAsset deterministicDiscreteNNModel; + ModelAsset deterministicContinuousNNModel; + Test3DSensorComponent sensor_21_20_3; + Test3DSensorComponent sensor_20_22_3; + + + ActionSpec GetContinuous2vis8vec2actionActionSpec() + { + return ActionSpec.MakeContinuous(2); + } + + ActionSpec GetDiscrete1vis0vec_2_3action_recurrModelActionSpec() + { + return ActionSpec.MakeDiscrete(2, 3); + } + + ActionSpec GetHybrid0vis53vec_3c_2dActionSpec() + { + return new ActionSpec(3, new[] { 2 }); + } + + [SetUp] + public void SetUp() + { + hybridONNXModelV2 = (ModelAsset)AssetDatabase.LoadAssetAtPath(k_hybrid_ONNX_recurr_v2, typeof(ModelAsset)); + + continuousONNXModel = (ModelAsset)AssetDatabase.LoadAssetAtPath(k_continuousONNXPath, typeof(ModelAsset)); + discreteONNXModel = (ModelAsset)AssetDatabase.LoadAssetAtPath(k_discreteONNXPath, typeof(ModelAsset)); + hybridONNXModel = (ModelAsset)AssetDatabase.LoadAssetAtPath(k_hybridONNXPath, typeof(ModelAsset)); + // continuousNNModel = (Model)AssetDatabase.LoadAssetAtPath(k_continuousNNPath, typeof(NNModel)); + // discreteNNModel = (Model)AssetDatabase.LoadAssetAtPath(k_discreteNNPath, typeof(NNModel)); + deterministicDiscreteNNModel = (ModelAsset)AssetDatabase.LoadAssetAtPath(k_deterministic_discreteNNPath, typeof(ModelAsset)); + deterministicContinuousNNModel = (ModelAsset)AssetDatabase.LoadAssetAtPath(k_deterministic_continuousNNPath, typeof(ModelAsset)); + var go = new GameObject("SensorA"); + sensor_21_20_3 = go.AddComponent(); + sensor_21_20_3.Sensor = new Test3DSensor("SensorA", 21, 20, 3); + sensor_20_22_3 = go.AddComponent(); + sensor_20_22_3.Sensor = new Test3DSensor("SensorB", 20, 22, 3); + } + + [Test] + public void TestModelExist() + { + Assert.IsNotNull(continuousONNXModel); + Assert.IsNotNull(discreteONNXModel); + Assert.IsNotNull(hybridONNXModel); + // Assert.IsNotNull(continuousNNModel); + // Assert.IsNotNull(discreteNNModel); + Assert.IsNotNull(hybridONNXModelV2); + Assert.IsNotNull(deterministicDiscreteNNModel); + Assert.IsNotNull(deterministicContinuousNNModel); + } + + [Test] + public void TestCreation() + { + var inferenceDevice = InferenceDevice.Burst; + var modelRunner = new ModelRunner(continuousONNXModel, GetContinuous2vis8vec2actionActionSpec(), inferenceDevice); + modelRunner.Dispose(); + Assert.Throws(() => + { + // Cannot load a model trained with 1.x that has an LSTM + modelRunner = new ModelRunner(discreteONNXModel, GetDiscrete1vis0vec_2_3action_recurrModelActionSpec(), inferenceDevice); + modelRunner.Dispose(); + }); + modelRunner = new ModelRunner(hybridONNXModel, GetHybrid0vis53vec_3c_2dActionSpec(), inferenceDevice); + modelRunner.Dispose(); + // modelRunner = new ModelRunner(continuousNNModel, GetContinuous2vis8vec2actionActionSpec(), inferenceDevice); + // modelRunner.Dispose(); + + // Assert.Throws(() => + // { + // Cannot load a model trained with 1.x that has an LSTM + // modelRunner = new ModelRunner(discreteNNModel, GetDiscrete1vis0vec_2_3action_recurrModelActionSpec(), inferenceDevice); + // modelRunner.Dispose(); + // }); + // This one was trained with 2.0 so it should not raise an error: + modelRunner = new ModelRunner(hybridONNXModelV2, new ActionSpec(2, new[] { 2, 3 }), inferenceDevice); + modelRunner.Dispose(); + + // V2.0 Model that has serialized deterministic action tensors, discrete + modelRunner = new ModelRunner(deterministicDiscreteNNModel, new ActionSpec(0, new[] { 7 }), inferenceDevice); + modelRunner.Dispose(); + // V2.0 Model that has serialized deterministic action tensors, continuous + modelRunner = new ModelRunner(deterministicContinuousNNModel, + GetContinuous2vis8vec2actionActionSpec(), inferenceDevice, + deterministicInference: true); + modelRunner.Dispose(); + } + + [Test] + public void TestHasModel() + { + var modelRunner = new ModelRunner(continuousONNXModel, GetContinuous2vis8vec2actionActionSpec(), InferenceDevice.Burst); + Assert.True(modelRunner.HasModel(continuousONNXModel, InferenceDevice.Burst)); + Assert.False(modelRunner.HasModel(continuousONNXModel, InferenceDevice.ComputeShader)); + Assert.False(modelRunner.HasModel(discreteONNXModel, InferenceDevice.Burst)); + modelRunner.Dispose(); + } + + [Test] + public void TestRunModel() + { + var actionSpec = GetContinuous2vis8vec2actionActionSpec(); + var modelRunner = new ModelRunner(continuousONNXModel, actionSpec, InferenceDevice.Burst); + var sensor_8 = new Sensors.VectorSensor(8, "VectorSensor8"); + var info1 = new AgentInfo(); + info1.episodeId = 1; + modelRunner.PutObservations(info1, new[] + { + sensor_8, + sensor_21_20_3.CreateSensors()[0], + sensor_20_22_3.CreateSensors()[0] + }.ToList()); + var info2 = new AgentInfo(); + info2.episodeId = 2; + modelRunner.PutObservations(info2, new[] + { + sensor_8, + sensor_21_20_3.CreateSensors()[0], + sensor_20_22_3.CreateSensors()[0] + }.ToList()); + + modelRunner.DecideBatch(); + + Assert.IsFalse(modelRunner.GetAction(1).Equals(ActionBuffers.Empty)); + Assert.IsFalse(modelRunner.GetAction(2).Equals(ActionBuffers.Empty)); + Assert.IsTrue(modelRunner.GetAction(3).Equals(ActionBuffers.Empty)); + Assert.AreEqual(actionSpec.NumDiscreteActions, modelRunner.GetAction(1).DiscreteActions.Length); + modelRunner.Dispose(); + } + + [Test] + public void TestRunModel_stochastic() + { + var actionSpec = GetContinuous2vis8vec2actionActionSpec(); + // deterministicInference = false by default + var modelRunner = new ModelRunner(deterministicContinuousNNModel, actionSpec, InferenceDevice.Burst); + var sensor_8 = new Sensors.VectorSensor(8, "VectorSensor8"); + var info1 = new AgentInfo(); + var obs = new[] + { + sensor_8, + sensor_21_20_3.CreateSensors()[0], + sensor_20_22_3.CreateSensors()[0] + }.ToList(); + info1.episodeId = 1; + modelRunner.PutObservations(info1, obs); + modelRunner.DecideBatch(); + var stochAction1 = (float[])modelRunner.GetAction(1).ContinuousActions.Array.Clone(); + + modelRunner.PutObservations(info1, obs); + modelRunner.DecideBatch(); + var stochAction2 = (float[])modelRunner.GetAction(1).ContinuousActions.Array.Clone(); + // Stochastic action selection should output randomly different action values with same obs + Assert.IsFalse(Enumerable.SequenceEqual(stochAction1, stochAction2, new FloatThresholdComparer(0.001f))); + modelRunner.Dispose(); + } + + [Test] + public void TestRunModel_deterministic() + { + var actionSpec = GetContinuous2vis8vec2actionActionSpec(); + var modelRunner = new ModelRunner(deterministicContinuousNNModel, actionSpec, InferenceDevice.Burst); + var sensor_8 = new Sensors.VectorSensor(8, "VectorSensor8"); + var info1 = new AgentInfo(); + var obs = new[] + { + sensor_8, + sensor_21_20_3.CreateSensors()[0], + sensor_20_22_3.CreateSensors()[0] + }.ToList(); + var deterministicModelRunner = new ModelRunner(deterministicContinuousNNModel, actionSpec, InferenceDevice.Burst, + deterministicInference: true); + info1.episodeId = 1; + deterministicModelRunner.PutObservations(info1, obs); + deterministicModelRunner.DecideBatch(); + var deterministicAction1 = (float[])deterministicModelRunner.GetAction(1).ContinuousActions.Array.Clone(); + + deterministicModelRunner.PutObservations(info1, obs); + deterministicModelRunner.DecideBatch(); + var deterministicAction2 = (float[])deterministicModelRunner.GetAction(1).ContinuousActions.Array.Clone(); + // Deterministic action selection should output same action everytime + Assert.IsTrue(Enumerable.SequenceEqual(deterministicAction1, deterministicAction2, new FloatThresholdComparer(0.001f))); + modelRunner.Dispose(); + } + } +} diff --git a/com.unity.ml-agents.tests/Tests/Editor/Inference/ModelRunnerTest.cs.meta b/com.unity.ml-agents.tests/Tests/Editor/Inference/ModelRunnerTest.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..273d3bb579936f3734845e7ac62e5c133876e1ac Binary files /dev/null and b/com.unity.ml-agents.tests/Tests/Editor/Inference/ModelRunnerTest.cs.meta differ diff --git a/com.unity.ml-agents.tests/Tests/Editor/Inference/ParameterLoaderTest.cs b/com.unity.ml-agents.tests/Tests/Editor/Inference/ParameterLoaderTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..811b05c30d72ba834dff0e8c40d06a668eac249b --- /dev/null +++ b/com.unity.ml-agents.tests/Tests/Editor/Inference/ParameterLoaderTest.cs @@ -0,0 +1,542 @@ +using System.Linq; +using NUnit.Framework; +using UnityEngine; +using UnityEditor; +using Unity.InferenceEngine; +using Unity.MLAgents.Actuators; +using Unity.MLAgents.Inference; +using Unity.MLAgents.Sensors; +using Unity.MLAgents.Policies; + +namespace Unity.MLAgents.Tests +{ + public class Test3DSensorComponent : SensorComponent + { + public ISensor Sensor; + + public override ISensor[] CreateSensors() + { + return new ISensor[] { Sensor }; + } + } + + public class Test3DSensor : ISensor, IBuiltInSensor + { + int m_Width; + int m_Height; + int m_Channels; + string m_Name; + // Dummy value for the IBuiltInSensor interface + public const int k_BuiltInSensorType = -42; + + public Test3DSensor(string name, int width, int height, int channels) + { + m_Width = width; + m_Height = height; + m_Channels = channels; + m_Name = name; + } + + public ObservationSpec GetObservationSpec() + { + return ObservationSpec.Visual(m_Channels, m_Height, m_Width); + } + + public int Write(ObservationWriter writer) + { + for (int i = 0; i < m_Width * m_Height * m_Channels; i++) + { + writer[i] = 0.0f; + } + return m_Width * m_Height * m_Channels; + } + + public byte[] GetCompressedObservation() + { + return new byte[0]; + } + + public void Update() { } + public void Reset() { } + + public CompressionSpec GetCompressionSpec() + { + return CompressionSpec.Default(); + } + + public string GetName() + { + return m_Name; + } + + public BuiltInSensorType GetBuiltInSensorType() + { + return (BuiltInSensorType)k_BuiltInSensorType; + } + } + + [TestFixture] + public class ParameterLoaderTest + { + const string k_discrete_ONNX_v2 = "Packages/com.unity.ml-agents.tests/Tests/Editor/TestModels/discrete_rank2_vector_v2_0.onnx"; + const string k_hybrid_ONNX_recurr_v2 = "Packages/com.unity.ml-agents.tests/Tests/Editor/TestModels/hybrid0vis8vec_2c_2_3d_v2_0.onnx"; + + + // ONNX model with continuous/discrete action output (support hybrid action) + const string k_continuousONNXPath = "Packages/com.unity.ml-agents.tests/Tests/Editor/TestModels/continuous2vis8vec2action_v1_0.onnx"; + const string k_discreteONNXPath = "Packages/com.unity.ml-agents.tests/Tests/Editor/TestModels/discrete1vis0vec_2_3action_obsolete_recurr_v1_0.onnx"; + const string k_hybridONNXPath = "Packages/com.unity.ml-agents.tests/Tests/Editor/TestModels/hybrid0vis53vec_3c_2daction_v1_0.onnx"; + // NN model with single action output (deprecated, does not support hybrid action). + // Same BrainParameters settings as the corresponding ONNX model. + + ModelAsset rank2ONNXModel; + ModelAsset hybridRecurrV2Model; + ModelAsset continuousONNXModel; + ModelAsset discreteONNXModel; + ModelAsset hybridONNXModel; + Test3DSensorComponent sensor_21_20_3; + Test3DSensorComponent sensor_20_22_3; + BufferSensor sensor_23_20; + VectorSensor sensor_8; + VectorSensor sensor_10; + + BrainParameters GetContinuous2vis8vec2actionBrainParameters() + { + var validBrainParameters = new BrainParameters(); + validBrainParameters.VectorObservationSize = 8; + validBrainParameters.NumStackedVectorObservations = 1; + validBrainParameters.ActionSpec = ActionSpec.MakeContinuous(2); + return validBrainParameters; + } + + BrainParameters GetDiscrete1vis0vec_2_3action_recurrModelBrainParameters() + { + var validBrainParameters = new BrainParameters(); + validBrainParameters.VectorObservationSize = 0; + validBrainParameters.NumStackedVectorObservations = 1; + validBrainParameters.ActionSpec = ActionSpec.MakeDiscrete(2, 3); + return validBrainParameters; + } + + BrainParameters GetHybridBrainParameters() + { + var validBrainParameters = new BrainParameters(); + validBrainParameters.VectorObservationSize = 53; + validBrainParameters.NumStackedVectorObservations = 1; + validBrainParameters.ActionSpec = new ActionSpec(3, new[] { 2 }); + return validBrainParameters; + } + + BrainParameters GetRank2BrainParameters() + { + var validBrainParameters = new BrainParameters(); + validBrainParameters.VectorObservationSize = 4; + validBrainParameters.NumStackedVectorObservations = 2; + validBrainParameters.ActionSpec = ActionSpec.MakeDiscrete(3, 3, 3); + return validBrainParameters; + } + + BrainParameters GetRecurrHybridBrainParameters() + { + var validBrainParameters = new BrainParameters(); + validBrainParameters.VectorObservationSize = 8; + validBrainParameters.NumStackedVectorObservations = 1; + validBrainParameters.ActionSpec = new ActionSpec(2, new int[] { 2, 3 }); + return validBrainParameters; + } + + [SetUp] + public void SetUp() + { + continuousONNXModel = (ModelAsset)AssetDatabase.LoadAssetAtPath(k_continuousONNXPath, typeof(ModelAsset)); + discreteONNXModel = (ModelAsset)AssetDatabase.LoadAssetAtPath(k_discreteONNXPath, typeof(ModelAsset)); + hybridONNXModel = (ModelAsset)AssetDatabase.LoadAssetAtPath(k_hybridONNXPath, typeof(ModelAsset)); + rank2ONNXModel = (ModelAsset)AssetDatabase.LoadAssetAtPath(k_discrete_ONNX_v2, typeof(ModelAsset)); + hybridRecurrV2Model = (ModelAsset)AssetDatabase.LoadAssetAtPath(k_hybrid_ONNX_recurr_v2, typeof(ModelAsset)); + var go = new GameObject("SensorA"); + sensor_21_20_3 = go.AddComponent(); + sensor_21_20_3.Sensor = new Test3DSensor("SensorA", 21, 20, 3); + sensor_20_22_3 = go.AddComponent(); + sensor_20_22_3.Sensor = new Test3DSensor("SensorA", 20, 22, 3); + sensor_23_20 = new BufferSensor(20, 23, "BufferSensor"); + sensor_8 = new VectorSensor(8, "VectorSensor8"); + sensor_10 = new VectorSensor(10, "VectorSensor10"); + } + + [Test] + public void TestModelExist() + { + Assert.IsNotNull(continuousONNXModel); + Assert.IsNotNull(discreteONNXModel); + Assert.IsNotNull(hybridONNXModel); + Assert.IsNotNull(rank2ONNXModel); + Assert.IsNotNull(hybridRecurrV2Model); + } + + [Test] + public void TestGetInputTensorsContinuous() + { + var model = ModelLoader.Load(continuousONNXModel); + var modelInfo = new SentisModelInfo(model); + var inputNames = modelInfo.InputNames; + // Model should contain 3 inputs : vector, visual 1 and visual 2 + Assert.AreEqual(3, inputNames.Count()); + Assert.Contains(TensorNames.VectorObservationPlaceholder, inputNames); + Assert.Contains(TensorNames.VisualObservationPlaceholderPrefix + "0", inputNames); + Assert.Contains(TensorNames.VisualObservationPlaceholderPrefix + "1", inputNames); + + Assert.AreEqual(2, modelInfo.NumVisualInputs); + + modelInfo.Dispose(); + } + + public void TestGetInputTensorsDiscrete() + { + var model = ModelLoader.Load(discreteONNXModel); + var modelInfo = new SentisModelInfo(model); + var inputNames = modelInfo.InputNames; + // Model should contain 2 inputs : recurrent and visual 1 + + Assert.Contains(TensorNames.VisualObservationPlaceholderPrefix + "0", inputNames); + // TODO :There are some memory tensors as well + modelInfo.Dispose(); + } + + [Test] + public void TestGetInputTensorsHybrid() + { + var model = ModelLoader.Load(hybridONNXModel); + var modelInfo = new SentisModelInfo(model); + var inputNames = modelInfo.InputNames; + Assert.Contains(TensorNames.VectorObservationPlaceholder, inputNames); + modelInfo.Dispose(); + } + + [Test] + public void TestGetOutputTensorsContinuous() + { + var model = ModelLoader.Load(continuousONNXModel); + var modelInfo = new SentisModelInfo(model); + var outputNames = modelInfo.OutputNames; + var actionOutputName = TensorNames.ContinuousActionOutput; + Assert.Contains(actionOutputName, outputNames); + Assert.AreEqual(1, outputNames.Count()); + modelInfo.Dispose(); + } + + [Test] + public void TestGetOutputTensorsDiscrete() + { + var model = ModelLoader.Load(discreteONNXModel); + var modelInfo = new SentisModelInfo(model); + var outputNames = modelInfo.OutputNames; + var actionOutputName = TensorNames.DiscreteActionOutput; + Assert.Contains(actionOutputName, outputNames); + // TODO : There are some memory tensors as well + modelInfo.Dispose(); + } + + [Test] + public void TestGetOutputTensorsHybrid() + { + var model = ModelLoader.Load(hybridONNXModel); + var modelInfo = new SentisModelInfo(model); + var outputNames = modelInfo.OutputNames; + + Assert.AreEqual(2, outputNames.Count()); + Assert.Contains(TensorNames.ContinuousActionOutput, outputNames); + Assert.Contains(TensorNames.DiscreteActionOutput, outputNames); + + modelInfo.Dispose(); + } + + [Test] + public void TestCheckModelRank2() + { + var model = ModelLoader.Load(rank2ONNXModel); + var validBrainParameters = GetRank2BrainParameters(); + + var errors = SentisModelParamLoader.CheckModel( + model, validBrainParameters, + new ISensor[] { sensor_23_20, sensor_10, sensor_8 }, new ActuatorComponent[0] + ); + Assert.AreEqual(0, errors.Count()); // There should not be any errors + + errors = SentisModelParamLoader.CheckModel( + model, validBrainParameters, + new ISensor[] { sensor_23_20, sensor_10 }, new ActuatorComponent[0] + ); + Assert.AreNotEqual(0, errors.Count()); // Wrong number of sensors + + errors = SentisModelParamLoader.CheckModel( + model, validBrainParameters, + new ISensor[] { new BufferSensor(20, 40, "BufferSensor"), sensor_10, sensor_8 }, new ActuatorComponent[0] + ); + Assert.AreNotEqual(0, errors.Count()); // Wrong buffer sensor size + + errors = SentisModelParamLoader.CheckModel( + model, validBrainParameters, + new ISensor[] { sensor_23_20, sensor_10, sensor_10 }, new ActuatorComponent[0] + ); + Assert.AreNotEqual(0, errors.Count()); // Wrong vector sensor size + } + + [Test] + public void TestCheckModelValidContinuous() + { + var model = ModelLoader.Load(continuousONNXModel); + var validBrainParameters = GetContinuous2vis8vec2actionBrainParameters(); + + var errors = SentisModelParamLoader.CheckModel( + model, validBrainParameters, + new ISensor[] + { + new VectorSensor(8), + sensor_21_20_3.CreateSensors()[0], + sensor_20_22_3.CreateSensors()[0] + }, + new ActuatorComponent[0] + ); + Assert.AreEqual(0, errors.Count()); // There should not be any errors + } + + [Test] + public void TestCheckModelValidDiscrete() + { + var model = ModelLoader.Load(discreteONNXModel); + var validBrainParameters = GetDiscrete1vis0vec_2_3action_recurrModelBrainParameters(); + + var errors = SentisModelParamLoader.CheckModel( + model, validBrainParameters, + new ISensor[] { sensor_21_20_3.CreateSensors()[0] }, new ActuatorComponent[0] + ); + foreach (var e in errors) + { + Debug.Log(e.Message); + } + Assert.Greater(errors.Count(), 0); // There should be an error since LSTM v1.x is not supported + } + + [Test] + public void TestCheckModelValidRecurrent() + { + var model = ModelLoader.Load(hybridRecurrV2Model); + var num_errors = 0; // A model trained with v2 should not raise errors + var validBrainParameters = GetRecurrHybridBrainParameters(); + + var errors = SentisModelParamLoader.CheckModel( + model, validBrainParameters, + new ISensor[] { sensor_8 }, new ActuatorComponent[0] + ); + Assert.AreEqual(num_errors, errors.Count()); // There should not be any errors + + var invalidBrainParameters = GetRecurrHybridBrainParameters(); + invalidBrainParameters.ActionSpec = new ActionSpec(1, new int[] { 2, 3 }); + errors = SentisModelParamLoader.CheckModel( + model, invalidBrainParameters, + new ISensor[] { sensor_8 }, new ActuatorComponent[0] + ); + Assert.AreEqual(1, errors.Count()); // 1 continuous action instead of 2 + + invalidBrainParameters.ActionSpec = new ActionSpec(2, new int[] { 3, 2 }); + errors = SentisModelParamLoader.CheckModel( + model, invalidBrainParameters, + new ISensor[] { sensor_8 }, new ActuatorComponent[0] + ); + Assert.AreEqual(1, errors.Count()); // Discrete action branches flipped + } + + [Test] + public void TestCheckModelValidHybrid() + { + var model = ModelLoader.Load(hybridONNXModel); + var validBrainParameters = GetHybridBrainParameters(); + + var errors = SentisModelParamLoader.CheckModel( + model, validBrainParameters, + new ISensor[] + { + new VectorSensor(validBrainParameters.VectorObservationSize) + }, new ActuatorComponent[0] + ); + Assert.AreEqual(0, errors.Count()); // There should not be any errors + } + + [Test] + public void TestCheckModelThrowsVectorObservationContinuous() + { + var model = ModelLoader.Load(continuousONNXModel); + + var brainParameters = GetContinuous2vis8vec2actionBrainParameters(); + brainParameters.VectorObservationSize = 9; // Invalid observation + var errors = SentisModelParamLoader.CheckModel( + model, brainParameters, + new ISensor[] + { + sensor_21_20_3.CreateSensors()[0], + sensor_20_22_3.CreateSensors()[0] + }, + new ActuatorComponent[0] + ); + Assert.Greater(errors.Count(), 0); + + brainParameters = GetContinuous2vis8vec2actionBrainParameters(); + brainParameters.NumStackedVectorObservations = 2;// Invalid stacking + errors = SentisModelParamLoader.CheckModel( + model, brainParameters, + new ISensor[] + { + sensor_21_20_3.CreateSensors()[0], + sensor_20_22_3.CreateSensors()[0] + }, + new ActuatorComponent[0] + ); + Assert.Greater(errors.Count(), 0); + } + + [Test] + public void TestCheckModelThrowsVectorObservationDiscrete() + { + var model = ModelLoader.Load(discreteONNXModel); + + var brainParameters = GetDiscrete1vis0vec_2_3action_recurrModelBrainParameters(); + brainParameters.VectorObservationSize = 1; // Invalid observation + var errors = SentisModelParamLoader.CheckModel( + model, brainParameters, new ISensor[] + { + sensor_21_20_3.CreateSensors()[0] + }, + new ActuatorComponent[0] + ); + Assert.Greater(errors.Count(), 0); + } + + [Test] + public void TestCheckModelThrowsVectorObservationHybrid() + { + var model = ModelLoader.Load(hybridONNXModel); + + var brainParameters = GetHybridBrainParameters(); + brainParameters.VectorObservationSize = 9; // Invalid observation + var errors = SentisModelParamLoader.CheckModel( + model, brainParameters, + new ISensor[] { }, new ActuatorComponent[0] + ); + Assert.Greater(errors.Count(), 0); + + brainParameters = GetContinuous2vis8vec2actionBrainParameters(); + brainParameters.NumStackedVectorObservations = 2;// Invalid stacking + errors = SentisModelParamLoader.CheckModel( + model, brainParameters, + new ISensor[] { }, new ActuatorComponent[0] + ); + Assert.Greater(errors.Count(), 0); + } + + [Test] + public void TestCheckModelThrowsActionContinuous() + { + var model = ModelLoader.Load(continuousONNXModel); + + var brainParameters = GetContinuous2vis8vec2actionBrainParameters(); + brainParameters.ActionSpec = ActionSpec.MakeContinuous(3); // Invalid action + var errors = SentisModelParamLoader.CheckModel( + model, brainParameters, new ISensor[] + { + sensor_21_20_3.CreateSensors()[0], + sensor_20_22_3.CreateSensors()[0] + }, + new ActuatorComponent[0] + ); + Assert.Greater(errors.Count(), 0); + + brainParameters = GetContinuous2vis8vec2actionBrainParameters(); + brainParameters.ActionSpec = ActionSpec.MakeDiscrete(3); // Invalid SpaceType + errors = SentisModelParamLoader.CheckModel( + model, brainParameters, new ISensor[] + { + sensor_21_20_3.CreateSensors()[0], + sensor_20_22_3.CreateSensors()[0] + }, + new ActuatorComponent[0] + ); + Assert.Greater(errors.Count(), 0); + } + + [Test] + public void TestCheckModelThrowsActionDiscrete() + { + var model = ModelLoader.Load(discreteONNXModel); + + var brainParameters = GetDiscrete1vis0vec_2_3action_recurrModelBrainParameters(); + brainParameters.ActionSpec = ActionSpec.MakeDiscrete(3, 3); // Invalid action + var errors = SentisModelParamLoader.CheckModel( + model, brainParameters, + new ISensor[] { sensor_21_20_3.CreateSensors()[0] }, + new ActuatorComponent[0] + ); + Assert.Greater(errors.Count(), 0); + + brainParameters = GetContinuous2vis8vec2actionBrainParameters(); + brainParameters.ActionSpec = ActionSpec.MakeContinuous(2); // Invalid SpaceType + errors = SentisModelParamLoader.CheckModel( + model, + brainParameters, + new ISensor[] { sensor_21_20_3.CreateSensors()[0] }, + new ActuatorComponent[0] + ); + Assert.Greater(errors.Count(), 0); + } + + [Test] + public void TestCheckModelThrowsActionHybrid() + { + var model = ModelLoader.Load(hybridONNXModel); + + var brainParameters = GetHybridBrainParameters(); + brainParameters.ActionSpec = new ActionSpec(3, new[] { 3 }); // Invalid discrete action size + var errors = SentisModelParamLoader.CheckModel( + model, + brainParameters, + new ISensor[] + { + sensor_21_20_3.CreateSensors()[0], + sensor_20_22_3.CreateSensors()[0] + }, + new ActuatorComponent[0] + ); + Assert.Greater(errors.Count(), 0); + + brainParameters = GetContinuous2vis8vec2actionBrainParameters(); + brainParameters.ActionSpec = ActionSpec.MakeDiscrete(2); // Missing continuous action + errors = SentisModelParamLoader.CheckModel( + model, + brainParameters, + new ISensor[] + { + sensor_21_20_3.CreateSensors()[0], + sensor_20_22_3.CreateSensors()[0] + }, + new ActuatorComponent[0] + ); + Assert.Greater(errors.Count(), 0); + } + + [Test] + public void TestCheckModelThrowsNoModel() + { + var brainParameters = GetContinuous2vis8vec2actionBrainParameters(); + var errors = SentisModelParamLoader.CheckModel( + null, + brainParameters, + new ISensor[] + { + sensor_21_20_3.CreateSensors()[0], + sensor_20_22_3.CreateSensors()[0] + }, + new ActuatorComponent[0] + ); + Assert.Greater(errors.Count(), 0); + } + } +} diff --git a/com.unity.ml-agents.tests/Tests/Editor/Inference/ParameterLoaderTest.cs.meta b/com.unity.ml-agents.tests/Tests/Editor/Inference/ParameterLoaderTest.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..25bff938efabf5307993759207c074c680d97774 Binary files /dev/null and b/com.unity.ml-agents.tests/Tests/Editor/Inference/ParameterLoaderTest.cs.meta differ diff --git a/com.unity.ml-agents.tests/Tests/Editor/Inference/TensorUtilsTest.cs b/com.unity.ml-agents.tests/Tests/Editor/Inference/TensorUtilsTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..412c999c52a0dbb8af91667736996b5bb327cb5b --- /dev/null +++ b/com.unity.ml-agents.tests/Tests/Editor/Inference/TensorUtilsTest.cs @@ -0,0 +1,132 @@ +using System; +using NUnit.Framework; +using Unity.InferenceEngine; +using Unity.MLAgents.Inference; +using Unity.MLAgents.Inference.Utils; + +namespace Unity.MLAgents.Tests +{ + public class TensorUtilsTest + { + [TestCase(4, TestName = "TestResizeTensor_4D")] + [TestCase(8, TestName = "TestResizeTensor_8D")] + public void TestResizeTensor(int dimension) + { + var height = 64; + var width = 84; + var channels = 3; + + // Set shape to {1, ..., channels, height, width} + // For 8D, the ... are all 1's + var shape = new int[dimension]; + for (var i = 0; i < dimension; i++) + { + shape[i] = 1; + } + shape[dimension - 3] = channels; + shape[dimension - 2] = height; + shape[dimension - 1] = width; + + var intShape = new int[dimension]; + for (var i = 0; i < dimension; i++) + { + intShape[i] = (int)shape[i]; + } + + var tensorProxy = new TensorProxy + { + valueType = TensorProxy.TensorType.Integer, + data = new Tensor(new TensorShape(intShape)), + shape = shape, + }; + + // These should be invariant after the resize. + Assert.AreEqual(height, tensorProxy.data.shape.Height()); + Assert.AreEqual(width, tensorProxy.data.shape.Width()); + Assert.AreEqual(channels, tensorProxy.data.shape.Channels()); + + // TODO this resize is changing the tensor dimensions.need fix. + TensorUtils.ResizeTensor(tensorProxy, 42); + + Assert.AreEqual(height, tensorProxy.shape[dimension - 2]); + Assert.AreEqual(width, tensorProxy.shape[dimension - 1]); + Assert.AreEqual(channels, tensorProxy.shape[dimension - 3]); + + Assert.AreEqual(height, tensorProxy.data.shape.Height()); + Assert.AreEqual(width, tensorProxy.data.shape.Width()); + Assert.AreEqual(channels, tensorProxy.data.shape.Channels()); + } + + [Test] + public void RandomNormalTestTensorInt() + { + var rn = new RandomNormal(1982); + var t = new TensorProxy + { + valueType = TensorProxy.TensorType.Integer + }; + + Assert.Throws( + () => TensorUtils.FillTensorWithRandomNormal(t, rn)); + } + + [Test] + public void RandomNormalTestDataNull() + { + var rn = new RandomNormal(1982); + var t = new TensorProxy + { + valueType = TensorProxy.TensorType.FloatingPoint + }; + + Assert.Throws( + () => TensorUtils.FillTensorWithRandomNormal(t, rn)); + } + + [Test] + public void RandomNormalTestTensor() + { + var rn = new RandomNormal(1982); + var t = new TensorProxy + { + valueType = TensorProxy.TensorType.FloatingPoint, + data = new Tensor(new TensorShape(1, 3, 4, 2)) + }; + + TensorUtils.FillTensorWithRandomNormal(t, rn); + + var reference = new[] + { + -0.4315872f, + -1.11074f, + 0.3414804f, + -1.130287f, + 0.1413168f, + -0.5105762f, + -0.3027347f, + -0.2645015f, + 1.225356f, + -0.02921959f, + 0.3716498f, + -1.092338f, + 0.9561074f, + -0.5018106f, + 1.167787f, + -0.7763879f, + -0.07491868f, + 0.5396146f, + -0.1377991f, + 0.3331701f, + 0.06144788f, + 0.9520947f, + 1.088157f, + -1.177194f, + }; + + for (var i = 0; i < t.data.Length(); i++) + { + Assert.AreEqual(((Tensor)t.data)[i], reference[i], 0.0001); + } + } + } +} diff --git a/com.unity.ml-agents.tests/Tests/Editor/Inference/TensorUtilsTest.cs.meta b/com.unity.ml-agents.tests/Tests/Editor/Inference/TensorUtilsTest.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..4141d495048ed8a176b8f7e36c35031f508017f8 Binary files /dev/null and b/com.unity.ml-agents.tests/Tests/Editor/Inference/TensorUtilsTest.cs.meta differ diff --git a/com.unity.ml-agents.tests/Tests/Editor/InplaceArrayTests.cs b/com.unity.ml-agents.tests/Tests/Editor/InplaceArrayTests.cs new file mode 100644 index 0000000000000000000000000000000000000000..2e8f21cf289f51bc444cececa0b125770adc0775 --- /dev/null +++ b/com.unity.ml-agents.tests/Tests/Editor/InplaceArrayTests.cs @@ -0,0 +1,190 @@ +using System; +using System.Collections; +using NUnit.Framework; + + +namespace Unity.MLAgents.Tests +{ + [TestFixture] + public class InplaceArrayTests + { + class LengthCases : IEnumerable + { + public IEnumerator GetEnumerator() + { + yield return 1; + yield return 2; + yield return 3; + yield return 4; + } + } + + private InplaceArray GetTestArray(int length) + { + switch (length) + { + case 1: + return new InplaceArray(11); + case 2: + return new InplaceArray(11, 22); + case 3: + return new InplaceArray(11, 22, 33); + case 4: + return new InplaceArray(11, 22, 33, 44); + default: + throw new ArgumentException("bad test!"); + } + } + + private InplaceArray GetZeroArray(int length) + { + switch (length) + { + case 1: + return new InplaceArray(0); + case 2: + return new InplaceArray(0, 0); + case 3: + return new InplaceArray(0, 0, 0); + case 4: + return new InplaceArray(0, 0, 0, 0); + default: + throw new ArgumentException("bad test!"); + } + } + + [Test] + public void TestInplaceArrayCtor() + { + var a1 = new InplaceArray(11); + Assert.AreEqual(1, a1.Length); + Assert.AreEqual(11, a1[0]); + + var a2 = new InplaceArray(11, 22); + Assert.AreEqual(2, a2.Length); + Assert.AreEqual(11, a2[0]); + Assert.AreEqual(22, a2[1]); + + var a3 = new InplaceArray(11, 22, 33); + Assert.AreEqual(3, a3.Length); + Assert.AreEqual(11, a3[0]); + Assert.AreEqual(22, a3[1]); + Assert.AreEqual(33, a3[2]); + + var a4 = new InplaceArray(11, 22, 33, 44); + Assert.AreEqual(4, a4.Length); + Assert.AreEqual(11, a4[0]); + Assert.AreEqual(22, a4[1]); + Assert.AreEqual(33, a4[2]); + Assert.AreEqual(44, a4[3]); + } + + [TestCaseSource(typeof(LengthCases))] + public void TestInplaceGetSet(int length) + { + var original = GetTestArray(length); + + for (var i = 0; i < original.Length; i++) + { + var modified = original; + modified[i] = 0; + for (var j = 0; j < original.Length; j++) + { + if (i == j) + { + // This is the one we overwrote + Assert.AreEqual(0, modified[j]); + } + else + { + // Other elements should be unchanged + Assert.AreEqual(original[j], modified[j]); + } + } + } + } + + [TestCaseSource(typeof(LengthCases))] + public void TestInvalidAccess(int length) + { + var tmp = 0; + var a = GetTestArray(length); + // get + Assert.Throws(() => { tmp += a[-1]; }); + Assert.Throws(() => { tmp += a[length]; }); + + // set + Assert.Throws(() => { a[-1] = 0; }); + Assert.Throws(() => { a[length] = 0; }); + + // Make sure temp is used + Assert.AreEqual(0, tmp); + } + + [Test] + public void TestOperatorEqualsDifferentLengths() + { + // Check arrays of different length are never equal (even if they have 0s in all elements) + for (var l1 = 1; l1 <= 4; l1++) + { + var a1 = GetZeroArray(l1); + for (var l2 = 1; l2 <= 4; l2++) + { + var a2 = GetZeroArray(l2); + if (l1 == l2) + { + Assert.AreEqual(a1, a2); + Assert.IsTrue(a1 == a2); + } + else + { + Assert.AreNotEqual(a1, a2); + Assert.IsTrue(a1 != a2); + } + } + } + } + + [TestCaseSource(typeof(LengthCases))] + public void TestOperatorEquals(int length) + { + for (var index = 0; index < length; index++) + { + var a1 = GetTestArray(length); + var a2 = GetTestArray(length); + Assert.AreEqual(a1, a2); + Assert.IsTrue(a1 == a2); + + a1[index] = 42; + Assert.AreNotEqual(a1, a2); + Assert.IsTrue(a1 != a2); + + a2[index] = 42; + Assert.AreEqual(a1, a2); + Assert.IsTrue(a1 == a2); + } + } + + [Test] + public void TestToString() + { + Assert.AreEqual("[1]", new InplaceArray(1).ToString()); + Assert.AreEqual("[1, 2]", new InplaceArray(1, 2).ToString()); + Assert.AreEqual("[1, 2, 3]", new InplaceArray(1, 2, 3).ToString()); + Assert.AreEqual("[1, 2, 3, 4]", new InplaceArray(1, 2, 3, 4).ToString()); + } + + [TestCaseSource(typeof(LengthCases))] + public void TestFromList(int length) + { + var intArray = new int[length]; + for (var i = 0; i < length; i++) + { + intArray[i] = (i + 1) * 11; // 11, 22, etc. + } + + var converted = InplaceArray.FromList(intArray); + Assert.AreEqual(GetTestArray(length), converted); + } + } +} diff --git a/com.unity.ml-agents.tests/Tests/Editor/InplaceArrayTests.cs.meta b/com.unity.ml-agents.tests/Tests/Editor/InplaceArrayTests.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..227738d65fd363b10bee518e2a0170279c2a7997 Binary files /dev/null and b/com.unity.ml-agents.tests/Tests/Editor/InplaceArrayTests.cs.meta differ diff --git a/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/AbstractBoardTests.cs b/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/AbstractBoardTests.cs new file mode 100644 index 0000000000000000000000000000000000000000..3b46c7ec3e23dbb863b3dbf23a46ebece55f147a --- /dev/null +++ b/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/AbstractBoardTests.cs @@ -0,0 +1,215 @@ +using System; +using System.Collections.Generic; +using UnityEngine; +using NUnit.Framework; +using Unity.MLAgents.Integrations.Match3; + +namespace Unity.MLAgents.Tests.Integrations.Match3 +{ + internal class StringBoard : AbstractBoard + { + internal int MaxRows; + internal int MaxColumns; + internal int NumCellTypes; + internal int NumSpecialTypes; + public int CurrentRows; + public int CurrentColumns; + + public override BoardSize GetMaxBoardSize() + { + return new BoardSize + { + Rows = MaxRows, + Columns = MaxColumns, + NumCellTypes = NumCellTypes, + NumSpecialTypes = NumSpecialTypes + }; + } + + public override BoardSize GetCurrentBoardSize() + { + return new BoardSize + { + Rows = CurrentRows, + Columns = CurrentColumns, + NumCellTypes = NumCellTypes, + NumSpecialTypes = NumSpecialTypes + }; + } + + private string[] m_Board; + private string[] m_Special; + + /// + /// Convert a string like "000\n010\n000" to a board representation + /// Row 0 is considered the bottom row + /// + /// + public void SetBoard(string newBoard) + { + m_Board = newBoard.Split((char[])null, StringSplitOptions.RemoveEmptyEntries); + MaxRows = m_Board.Length; + MaxColumns = m_Board[0].Length; + CurrentRows = MaxRows; + CurrentColumns = MaxColumns; + NumCellTypes = 0; + for (var r = 0; r < MaxRows; r++) + { + for (var c = 0; c < MaxColumns; c++) + { + NumCellTypes = Mathf.Max(NumCellTypes, 1 + GetCellType(r, c)); + } + } + } + + public void SetSpecial(string newSpecial) + { + m_Special = newSpecial.Split((char[])null, StringSplitOptions.RemoveEmptyEntries); + Debug.Assert(MaxRows == m_Special.Length); + Debug.Assert(MaxColumns == m_Special[0].Length); + NumSpecialTypes = 0; + for (var r = 0; r < MaxRows; r++) + { + for (var c = 0; c < MaxColumns; c++) + { + NumSpecialTypes = Mathf.Max(NumSpecialTypes, GetSpecialType(r, c)); + } + } + } + + public override bool MakeMove(Move m) + { + return true; + } + + public override bool IsMoveValid(Move m) + { + return SimpleIsMoveValid(m); + } + + public override int GetCellType(int row, int col) + { + if (row >= CurrentRows || col >= CurrentColumns) + { + throw new IndexOutOfRangeException("Tried to get celltype out of bounds"); + } + + var character = m_Board[m_Board.Length - 1 - row][col]; + return (character - '0'); + } + + public override int GetSpecialType(int row, int col) + { + if (row >= CurrentRows || col >= CurrentColumns) + { + throw new IndexOutOfRangeException("Tried to get specialtype out of bounds"); + } + + var character = m_Special[m_Board.Length - 1 - row][col]; + return (character - '0'); + } + } + + public class AbstractBoardTests + { + [Test] + public void TestBoardInit() + { + var boardString = +@"000 + 000 + 010"; + var gameObj = new GameObject("board"); + var board = gameObj.AddComponent(); + board.SetBoard(boardString); + + var boardSize = board.GetMaxBoardSize(); + + Assert.AreEqual(3, boardSize.Rows); + Assert.AreEqual(3, boardSize.Columns); + Assert.AreEqual(2, boardSize.NumCellTypes); + for (var r = 0; r < 3; r++) + { + for (var c = 0; c < 3; c++) + { + var expected = (r == 0 && c == 1) ? 1 : 0; + Assert.AreEqual(expected, board.GetCellType(r, c)); + } + } + } + + internal static List GetValidMoves4x4(bool fullBoard, BoardSize boardSize) + { + var validMoves = new List + { + Move.FromPositionAndDirection(2, 1, Direction.Down, boardSize), // equivalent to (1, 1, Up) + Move.FromPositionAndDirection(1, 1, Direction.Down, boardSize), + Move.FromPositionAndDirection(1, 1, Direction.Left, boardSize), + Move.FromPositionAndDirection(1, 1, Direction.Right, boardSize), + Move.FromPositionAndDirection(0, 1, Direction.Left, boardSize), + }; + + if (fullBoard) + { + // This would move out of range on the small board + // Equivalent to (3, 1, Down) + validMoves.Add(Move.FromPositionAndDirection(2, 1, Direction.Up, boardSize)); + + // These moves require matching with a cell that's off the small board, so they're invalid + // (even though the move itself doesn't go out of range). + validMoves.Add(Move.FromPositionAndDirection(2, 1, Direction.Left, boardSize)); // Equivalent to (2, 0, Right) + validMoves.Add(Move.FromPositionAndDirection(2, 1, Direction.Right, boardSize)); + } + + return validMoves; + } + + [TestCase(true, TestName = "Full Board")] + [TestCase(false, TestName = "Small Board")] + public void TestCheckValidMoves(bool fullBoard) + { + var gameObj = new GameObject("board"); + var board = gameObj.AddComponent(); + + var boardString = +@"0105 + 1024 + 0203 + 2022"; + board.SetBoard(boardString); + var boardSize = board.GetMaxBoardSize(); + if (!fullBoard) + { + board.CurrentRows -= 1; + } + + var validMoves = GetValidMoves4x4(fullBoard, boardSize); + + foreach (var m in validMoves) + { + Assert.IsTrue(board.IsMoveValid(m)); + } + + // Run through all moves and make sure those are the only valid ones + HashSet validIndices = new HashSet(); + foreach (var m in validMoves) + { + validIndices.Add(m.MoveIndex); + } + + // Make sure iterating over AllMoves is OK with the smaller board + foreach (var move in board.AllMoves()) + { + var expected = validIndices.Contains(move.MoveIndex); + Assert.AreEqual(expected, board.IsMoveValid(move), $"({move.Row}, {move.Column}, {move.Direction})"); + } + + HashSet validIndicesFromIterator = new HashSet(); + foreach (var move in board.ValidMoves()) + { + validIndicesFromIterator.Add(move.MoveIndex); + } + Assert.IsTrue(validIndices.SetEquals(validIndicesFromIterator)); + } + } +} diff --git a/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/AbstractBoardTests.cs.meta b/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/AbstractBoardTests.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..79da98cb7a5fedcb4cf3e40feb87ded218b35fb1 Binary files /dev/null and b/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/AbstractBoardTests.cs.meta differ diff --git a/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/Match3ActuatorTests.cs b/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/Match3ActuatorTests.cs new file mode 100644 index 0000000000000000000000000000000000000000..6711ed3b12e70467bf9d140e7aa63563a56c53f8 --- /dev/null +++ b/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/Match3ActuatorTests.cs @@ -0,0 +1,207 @@ +using System.Collections.Generic; +using NUnit.Framework; +using Unity.MLAgents.Actuators; +using Unity.MLAgents.Integrations.Match3; +using UnityEngine; + +namespace Unity.MLAgents.Tests.Integrations.Match3 +{ + internal class SimpleBoard : AbstractBoard + { + public int Rows; + public int Columns; + public int NumCellTypes; + public int NumSpecialTypes; + + public int LastMoveIndex; + public bool MovesAreValid = true; + + public bool CallbackCalled; + + public override BoardSize GetMaxBoardSize() + { + return new BoardSize + { + Rows = Rows, + Columns = Columns, + NumCellTypes = NumCellTypes, + NumSpecialTypes = NumSpecialTypes + }; + } + + public override int GetCellType(int row, int col) + { + return 0; + } + + public override int GetSpecialType(int row, int col) + { + return 0; + } + + public override bool IsMoveValid(Move m) + { + return MovesAreValid; + } + + public override bool MakeMove(Move m) + { + LastMoveIndex = m.MoveIndex; + return MovesAreValid; + } + + public void Callback() + { + CallbackCalled = true; + } + } + + public class Match3ActuatorTests + { + [SetUp] + public void SetUp() + { + if (Academy.IsInitialized) + { + Academy.Instance.Dispose(); + } + } + + [TestCase(true)] + [TestCase(false)] + public void TestValidMoves(bool movesAreValid) + { + // Check that a board with no valid moves doesn't raise an exception. + var gameObj = new GameObject(); + var board = gameObj.AddComponent(); + var agent = gameObj.AddComponent(); + gameObj.AddComponent(); + + board.Rows = 5; + board.Columns = 5; + board.NumCellTypes = 5; + board.NumSpecialTypes = 0; + + board.MovesAreValid = movesAreValid; + board.OnNoValidMovesAction = board.Callback; + board.LastMoveIndex = -1; + + agent.LazyInitialize(); + agent.RequestDecision(); + Academy.Instance.EnvironmentStep(); + + if (movesAreValid) + { + Assert.IsFalse(board.CallbackCalled); + } + else + { + Assert.IsTrue(board.CallbackCalled); + } + Assert.AreNotEqual(-1, board.LastMoveIndex); + } + + [Test] + public void TestActionSpec() + { + var gameObj = new GameObject(); + var board = gameObj.AddComponent(); + var actuator = gameObj.AddComponent(); + + board.Rows = 5; + board.Columns = 5; + board.NumCellTypes = 5; + board.NumSpecialTypes = 0; + + var actionSpec = actuator.ActionSpec; + Assert.AreEqual(1, actionSpec.NumDiscreteActions); + Assert.AreEqual(board.NumMoves(), actionSpec.BranchSizes[0]); + } + + [Test] + public void TestActionSpecNullBoard() + { + var gameObj = new GameObject(); + var actuator = gameObj.AddComponent(); + + var actionSpec = actuator.ActionSpec; + Assert.AreEqual(0, actionSpec.NumDiscreteActions); + Assert.AreEqual(0, actionSpec.NumContinuousActions); + } + + public class HashSetActionMask : IDiscreteActionMask + { + public HashSet[] HashSets; + public HashSetActionMask(ActionSpec spec) + { + HashSets = new HashSet[spec.NumDiscreteActions]; + for (var i = 0; i < spec.NumDiscreteActions; i++) + { + HashSets[i] = new HashSet(); + } + } + + public void SetActionEnabled(int branch, int actionIndex, bool isEnabled) + { + var hashSet = HashSets[branch]; + if (isEnabled) + { + hashSet.Remove(actionIndex); + } + else + { + hashSet.Add(actionIndex); + } + } + } + + [TestCase(true, TestName = "Full Board")] + [TestCase(false, TestName = "Small Board")] + public void TestMasking(bool fullBoard) + { + var gameObj = new GameObject("board"); + var board = gameObj.AddComponent(); + + var boardString = +@"0105 + 1024 + 0203 + 2022"; + board.SetBoard(boardString); + var boardSize = board.GetMaxBoardSize(); + if (!fullBoard) + { + board.CurrentRows -= 1; + } + + var validMoves = AbstractBoardTests.GetValidMoves4x4(fullBoard, boardSize); + + var actuatorComponent = gameObj.AddComponent(); + var actuator = actuatorComponent.CreateActuators()[0]; + + var masks = new HashSetActionMask(actuator.ActionSpec); + actuator.WriteDiscreteActionMask(masks); + + // Run through all moves and make sure those are the only valid ones + HashSet validIndices = new HashSet(); + foreach (var m in validMoves) + { + validIndices.Add(m.MoveIndex); + } + + // Valid moves and masked moves should be disjoint + Assert.IsFalse(validIndices.Overlaps(masks.HashSets[0])); + // And they should add up to all the potential moves + Assert.AreEqual(validIndices.Count + masks.HashSets[0].Count, board.NumMoves()); + } + + [Test] + public void TestNoBoardReturnsEmptyActuators() + { + var gameObj = new GameObject("board"); + var actuatorComponent = gameObj.AddComponent(); + var actuators = actuatorComponent.CreateActuators(); + Assert.AreEqual(0, actuators.Length); + } + } +} diff --git a/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/Match3ActuatorTests.cs.meta b/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/Match3ActuatorTests.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..3731b4e758021712ed04bfb6d049156637e0b86b Binary files /dev/null and b/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/Match3ActuatorTests.cs.meta differ diff --git a/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/Match3SensorTests.cs b/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/Match3SensorTests.cs new file mode 100644 index 0000000000000000000000000000000000000000..07c5a5b7f11cde7308d836d1bfb849ac54bde2ff --- /dev/null +++ b/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/Match3SensorTests.cs @@ -0,0 +1,431 @@ +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using NUnit.Framework; +using UnityEngine; +using Unity.MLAgents.Integrations.Match3; +using Unity.MLAgents.Sensors; + +namespace Unity.MLAgents.Tests.Integrations.Match3 +{ + public class Match3SensorTests + { + // Whether the expected PNG data should be written to a file. + // Only set this to true if the compressed observation format changes. + private bool WritePNGDataToFile = false; + private const string k_CellObservationPng = "match3obs_"; + private const string k_SpecialObservationPng = "match3obs_special_"; + private const string k_Suffix2x2 = "2x2_"; + + [TestCase(true, TestName = "Full Board")] + [TestCase(false, TestName = "Small Board")] + public void TestVectorObservations(bool fullBoard) + { + var boardString = +@"000 + 000 + 010"; + var gameObj = new GameObject("board"); + var board = gameObj.AddComponent(); + board.SetBoard(boardString); + if (!fullBoard) + { + board.CurrentRows = 2; + board.CurrentColumns = 2; + } + + var sensorComponent = gameObj.AddComponent(); + sensorComponent.ObservationType = Match3ObservationType.Vector; + var sensor = sensorComponent.CreateSensors()[0]; + + var expectedShape = new InplaceArray(3 * 3 * 2); + Assert.AreEqual(expectedShape, sensor.GetObservationSpec().Shape); + + float[] expectedObs; + + if (fullBoard) + { + expectedObs = new float[] + { + 1, 0, /* 0 */ 0, 1, /* 1 */ 1, 0, /* 0 */ + 1, 0, /* 0 */ 1, 0, /* 0 */ 1, 0, /* 0 */ + 1, 0, /* 0 */ 1, 0, /* 0 */ 1, 0, /* 0 */ + }; + } + else + { + expectedObs = new float[] + { + 1, 0, /* 0 */ 0, 1, /* 1 */ 0, 0, /* empty */ + 1, 0, /* 0 */ 1, 0, /* 0 */ 0, 0, /* empty */ + 0, 0, /* empty */ 0, 0, /* empty */ 0, 0, /* empty */ + }; + } + SensorTestHelper.CompareObservation(sensor, expectedObs); + } + + [Test] + public void TestVectorObservationsSpecial() + { + var boardString = +@"000 + 000 + 010"; + var specialString = +@"010 + 200 + 000"; + + var gameObj = new GameObject("board"); + var board = gameObj.AddComponent(); + board.SetBoard(boardString); + board.SetSpecial(specialString); + + var sensorComponent = gameObj.AddComponent(); + sensorComponent.ObservationType = Match3ObservationType.Vector; + var sensors = sensorComponent.CreateSensors(); + var cellSensor = sensors[0]; + var specialSensor = sensors[1]; + + { + var expectedShape = new InplaceArray(3 * 3 * 2); + Assert.AreEqual(expectedShape, cellSensor.GetObservationSpec().Shape); + + var expectedObs = new float[] + { + 1, 0, /* (0) */ 0, 1, /* (1) */ 1, 0, /* (0) */ + 1, 0, /* (0) */ 1, 0, /* (0) */ 1, 0, /* (0) */ + 1, 0, /* (0) */ 1, 0, /* (0) */ 1, 0, /* (0) */ + }; + SensorTestHelper.CompareObservation(cellSensor, expectedObs); + } + { + var expectedShape = new InplaceArray(3 * 3 * 3); + Assert.AreEqual(expectedShape, specialSensor.GetObservationSpec().Shape); + + var expectedObs = new float[] + { + 1, 0, 0, /* (0) */ 1, 0, 0, /* (1) */ 1, 0, 0, /* (0) */ + 0, 0, 1, /* (2) */ 1, 0, 0, /* (0) */ 1, 0, 0, /* (0) */ + 1, 0, 0, /* (0) */ 0, 1, 0, /* (1) */ 1, 0, 0, /* (0) */ + }; + SensorTestHelper.CompareObservation(specialSensor, expectedObs); + } + } + + [TestCase(true, TestName = "Full Board")] + [TestCase(false, TestName = "Small Board")] + public void TestVisualObservations(bool fullBoard) + { + var boardString = +@"000 + 000 + 010"; + var gameObj = new GameObject("board"); + var board = gameObj.AddComponent(); + board.SetBoard(boardString); + if (!fullBoard) + { + board.CurrentRows = 2; + board.CurrentColumns = 2; + } + + var sensorComponent = gameObj.AddComponent(); + sensorComponent.ObservationType = Match3ObservationType.UncompressedVisual; + var sensor = sensorComponent.CreateSensors()[0]; + + var expectedShape = new InplaceArray(2, 3, 3); + Assert.AreEqual(expectedShape, sensor.GetObservationSpec().Shape); + + Assert.AreEqual(SensorCompressionType.None, sensor.GetCompressionSpec().SensorCompressionType); + + float[] expectedObs; + float[,,] expectedObs3D; + + if (fullBoard) + { + expectedObs = new float[] + { + // NCHW layout: [channel0_all_positions, channel1_all_positions] + 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0 + }; + + expectedObs3D = new float[,,] + { { { 1, 0, 1 }, {1, 1, 1}, {1, 1, 1} }, { {0, 1, 0}, {0, 0, 0}, {0, 0, 0} } }; + } + else + { + expectedObs = new float[] + { + // NCHW layout: [channel0_all_positions, channel1_all_positions] + 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 + }; + + expectedObs3D = new float[,,] + { { {1, 0, 0}, {1, 1, 0}, {0, 0, 0} }, { {0, 1, 0}, {0, 0, 0}, {0, 0, 0} } }; + } + SensorTestHelper.CompareObservation(sensor, expectedObs); + SensorTestHelper.CompareObservation(sensor, expectedObs3D); + } + + [Test] + public void TestVisualObservationsSpecial() + { + var boardString = +@"000 + 000 + 010"; + var specialString = +@"010 + 200 + 000"; + + var gameObj = new GameObject("board"); + var board = gameObj.AddComponent(); + board.SetBoard(boardString); + board.SetSpecial(specialString); + + var sensorComponent = gameObj.AddComponent(); + sensorComponent.ObservationType = Match3ObservationType.UncompressedVisual; + var sensors = sensorComponent.CreateSensors(); + var cellSensor = sensors[0]; + var specialSensor = sensors[1]; + + { + var expectedShape = new InplaceArray(2, 3, 3); + Assert.AreEqual(expectedShape, cellSensor.GetObservationSpec().Shape); + + Assert.AreEqual(SensorCompressionType.None, cellSensor.GetCompressionSpec().SensorCompressionType); + + var expectedObs = new float[] + { + // NCHW layout: [channel0_all_positions, channel1_all_positions] + 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0 + }; + SensorTestHelper.CompareObservation(cellSensor, expectedObs); + + // var expectedObs3D = new float[,,] + // { + // {{1, 0}, {0, 1}, {1, 0}}, + // {{1, 0}, {1, 0}, {1, 0}}, + // {{1, 0}, {1, 0}, {1, 0}}, + // }; + + var expectedObs3D = new float[,,] + { + { + {1, 0, 1}, + {1, 1, 1}, + {1, 1, 1} + }, + { + {0, 1, 0}, + {0, 0, 0}, + {0, 0, 0} + } + }; + + SensorTestHelper.CompareObservation(cellSensor, expectedObs3D); + } + { + var expectedShape = new InplaceArray(3, 3, 3); + Assert.AreEqual(expectedShape, specialSensor.GetObservationSpec().Shape); + + Assert.AreEqual(SensorCompressionType.None, specialSensor.GetCompressionSpec().SensorCompressionType); + + var expectedObs = new float[] + { + // NCHW layout: [channel0_all_positions, channel1_all_positions, channel2_all_positions] + 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0 + }; + SensorTestHelper.CompareObservation(specialSensor, expectedObs); + + // var expectedObs3D = new float[,,] + // { + // {{1, 0, 0}, {1, 0, 0}, {1, 0, 0}}, + // {{0, 0, 1}, {1, 0, 0}, {1, 0, 0}}, + // {{1, 0, 0}, {0, 1, 0}, {1, 0, 0}}, + // }; + + var expectedObs3D = new float[,,] + { + { + {1, 1, 1}, + {0, 1, 1}, + {1, 0, 1} + }, + { + {0, 0, 0}, + {0, 0, 0}, + {0, 1, 0} + }, + { + {0, 0, 0}, + {1, 0, 0}, + {0, 0, 0} + } + }; + + SensorTestHelper.CompareObservation(specialSensor, expectedObs3D); + } + + // Test that Dispose() cleans up the component and its sensors + sensorComponent.Dispose(); + + var flags = BindingFlags.Instance | BindingFlags.NonPublic; + var componentSensors = (ISensor[])typeof(Match3SensorComponent).GetField("m_Sensors", flags).GetValue(sensorComponent); + Assert.IsNull(componentSensors); + var cellTexture = (Texture2D)typeof(Match3Sensor).GetField("m_ObservationTexture", flags).GetValue(cellSensor); + Assert.IsNull(cellTexture); + var specialTexture = (Texture2D)typeof(Match3Sensor).GetField("m_ObservationTexture", flags).GetValue(cellSensor); + Assert.IsNull(specialTexture); + } + + [TestCase(true, false, TestName = "Full Board, No Special")] + [TestCase(false, false, TestName = "Small Board, No Special")] + [TestCase(true, true, TestName = "Full Board, Special")] + [TestCase(false, true, TestName = "Small Board, Special")] + public void TestCompressedVisualObservationsSpecial(bool fullBoard, bool useSpecial) + { + var boardString = +@"003 + 000 + 010"; + var specialString = +@"014 + 200 + 000"; + + var gameObj = new GameObject("board"); + var board = gameObj.AddComponent(); + board.SetBoard(boardString); + var paths = new List { k_CellObservationPng }; + if (useSpecial) + { + board.SetSpecial(specialString); + paths.Add(k_SpecialObservationPng); + } + + if (!fullBoard) + { + // Shrink the board, and change the paths we're using for the ground truth PNGs + board.CurrentRows = 2; + board.CurrentColumns = 2; + for (var i = 0; i < paths.Count; i++) + { + paths[i] = paths[i] + k_Suffix2x2; + } + } + + var sensorComponent = gameObj.AddComponent(); + sensorComponent.ObservationType = Match3ObservationType.CompressedVisual; + var sensors = sensorComponent.CreateSensors(); + + var expectedNumChannels = new[] { 4, 5 }; + + for (var i = 0; i < paths.Count; i++) + { + var sensor = sensors[i]; + var expectedShape = new InplaceArray(expectedNumChannels[i], 3, 3); + Assert.AreEqual(expectedShape, sensor.GetObservationSpec().Shape); + + Assert.AreEqual(SensorCompressionType.PNG, sensor.GetCompressionSpec().SensorCompressionType); + + var pngData = sensor.GetCompressedObservation(); + if (WritePNGDataToFile) + { + // Enable this if the format of the observation changes + SavePNGs(pngData, paths[i]); + } + + var expectedPng = LoadPNGs(paths[i], 2); + Assert.AreEqual(expectedPng, pngData); + } + } + + /// + /// Helper method for un-concatenating PNG observations. + /// + /// + /// The PNG observations. + List SplitPNGs(byte[] concatenated) + { + var pngsOut = new List(); + var pngHeader = new byte[] { 137, 80, 78, 71, 13, 10, 26, 10 }; + + var current = new List(); + for (var i = 0; i < concatenated.Length; i++) + { + current.Add(concatenated[i]); + + // Check if the header starts at the next position + // If so, we'll start a new output array. + var headerIsNext = false; + if (i + 1 < concatenated.Length - pngHeader.Length) + { + for (var j = 0; j < pngHeader.Length; j++) + { + if (concatenated[i + 1 + j] != pngHeader[j]) + { + break; + } + + if (j == pngHeader.Length - 1) + { + headerIsNext = true; + } + } + } + + if (headerIsNext) + { + pngsOut.Add(current.ToArray()); + current = new List(); + } + } + pngsOut.Add(current.ToArray()); + + return pngsOut; + } + + void SavePNGs(byte[] concatenatedPngData, string pathPrefix) + { + var splitPngs = SplitPNGs(concatenatedPngData); + + for (var i = 0; i < splitPngs.Count; i++) + { + var pngData = splitPngs[i]; + var path = $"Packages/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/{pathPrefix}{i}.png"; + using (var sw = File.Create(path)) + { + foreach (var b in pngData) + { + sw.WriteByte(b); + } + } + } + } + + byte[] LoadPNGs(string pathPrefix, int numExpected) + { + var bytesOut = new List(); + for (var i = 0; i < numExpected; i++) + { + var path = $"Packages/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/{pathPrefix}{i}.png"; + var res = File.ReadAllBytes(path); + bytesOut.AddRange(res); + } + + return bytesOut.ToArray(); + } + + [Test] + public void TestNoBoardReturnsEmptySensors() + { + var gameObj = new GameObject("board"); + var sensorComponent = gameObj.AddComponent(); + var sensors = sensorComponent.CreateSensors(); + Assert.AreEqual(0, sensors.Length); + } + } +} diff --git a/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/Match3SensorTests.cs.meta b/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/Match3SensorTests.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..38a1a4d010697ae074e69bc91b2e9ce99439f978 Binary files /dev/null and b/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/Match3SensorTests.cs.meta differ diff --git a/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/MoveTests.cs b/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/MoveTests.cs new file mode 100644 index 0000000000000000000000000000000000000000..f4755b4860b7fc74cf5d14881cabc669faf1311f --- /dev/null +++ b/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/MoveTests.cs @@ -0,0 +1,65 @@ +using System; +using NUnit.Framework; +using Unity.MLAgents.Integrations.Match3; + +namespace Unity.MLAgents.Tests.Integrations.Match3 +{ + public class MoveTests + { + [Test] + public void TestMoveEquivalence() + { + var board10x10 = new BoardSize { Rows = 10, Columns = 10 }; + var moveUp = Move.FromPositionAndDirection(1, 1, Direction.Up, board10x10); + var moveDown = Move.FromPositionAndDirection(2, 1, Direction.Down, board10x10); + Assert.AreEqual(moveUp.MoveIndex, moveDown.MoveIndex); + + var moveRight = Move.FromPositionAndDirection(1, 1, Direction.Right, board10x10); + var moveLeft = Move.FromPositionAndDirection(1, 2, Direction.Left, board10x10); + Assert.AreEqual(moveRight.MoveIndex, moveLeft.MoveIndex); + } + + [Test] + public void TestNext() + { + var maxRows = 8; + var maxCols = 13; + var boardSize = new BoardSize + { + Rows = maxRows, + Columns = maxCols + }; + // make sure using Next agrees with FromMoveIndex. + var advanceMove = Move.FromMoveIndex(0, boardSize); + for (var moveIndex = 0; moveIndex < Move.NumPotentialMoves(boardSize); moveIndex++) + { + var moveFromIndex = Move.FromMoveIndex(moveIndex, boardSize); + Assert.AreEqual(advanceMove.MoveIndex, moveFromIndex.MoveIndex); + Assert.AreEqual(advanceMove.Row, moveFromIndex.Row); + Assert.AreEqual(advanceMove.Column, moveFromIndex.Column); + Assert.AreEqual(advanceMove.Direction, moveFromIndex.Direction); + + advanceMove.Next(boardSize); + } + } + + // These are off the board + [TestCase(-1, 5, Direction.Up)] + [TestCase(10, 5, Direction.Up)] + [TestCase(5, -1, Direction.Up)] + [TestCase(5, 10, Direction.Up)] + // These are on the board but would move off + [TestCase(0, 5, Direction.Down)] + [TestCase(9, 5, Direction.Up)] + [TestCase(5, 0, Direction.Left)] + [TestCase(5, 9, Direction.Right)] + public void TestInvalidMove(int row, int col, Direction dir) + { + var board10x10 = new BoardSize { Rows = 10, Columns = 10 }; + Assert.Throws(() => + { + Move.FromPositionAndDirection(row, col, dir, board10x10); + }); + } + } +} diff --git a/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/MoveTests.cs.meta b/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/MoveTests.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..e016865fcb566aeaea501afdccf99cf105c9b70c Binary files /dev/null and b/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/MoveTests.cs.meta differ diff --git a/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/match3obs_0.png b/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/match3obs_0.png new file mode 100644 index 0000000000000000000000000000000000000000..0743d0bc134ec0cc0a2ad89b3355f4443046cd01 Binary files /dev/null and b/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/match3obs_0.png differ diff --git a/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/match3obs_0.png.meta b/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/match3obs_0.png.meta new file mode 100644 index 0000000000000000000000000000000000000000..9faafa2e61b6bca6b4b3c7ec69e11df090b6e7fd Binary files /dev/null and b/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/match3obs_0.png.meta differ diff --git a/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/match3obs_1.png b/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/match3obs_1.png new file mode 100644 index 0000000000000000000000000000000000000000..afd50b9af82ee86f0e947e2fb064de756e19cadf Binary files /dev/null and b/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/match3obs_1.png differ diff --git a/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/match3obs_1.png.meta b/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/match3obs_1.png.meta new file mode 100644 index 0000000000000000000000000000000000000000..adbae821a03089654385bdf946f5fc5381f238de Binary files /dev/null and b/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/match3obs_1.png.meta differ diff --git a/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/match3obs_2x2_0.png b/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/match3obs_2x2_0.png new file mode 100644 index 0000000000000000000000000000000000000000..678315a87eb115daafe86707337bc2cc82864080 Binary files /dev/null and b/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/match3obs_2x2_0.png differ diff --git a/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/match3obs_2x2_0.png.meta b/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/match3obs_2x2_0.png.meta new file mode 100644 index 0000000000000000000000000000000000000000..7e8aa6438a2d53621d2dfe319bbd89939dfdc885 Binary files /dev/null and b/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/match3obs_2x2_0.png.meta differ diff --git a/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/match3obs_2x2_1.png b/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/match3obs_2x2_1.png new file mode 100644 index 0000000000000000000000000000000000000000..6f66a52754c4dbadae09817e056e6f19951889ab Binary files /dev/null and b/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/match3obs_2x2_1.png differ diff --git a/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/match3obs_2x2_1.png.meta b/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/match3obs_2x2_1.png.meta new file mode 100644 index 0000000000000000000000000000000000000000..8a8b427966e4b2d04a85c6a382982ba2885f84cb Binary files /dev/null and b/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/match3obs_2x2_1.png.meta differ diff --git a/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/match3obs_special_0.png b/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/match3obs_special_0.png new file mode 100644 index 0000000000000000000000000000000000000000..217e1f0b0aafc4928f5bebf50e6dc803805542ed Binary files /dev/null and b/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/match3obs_special_0.png differ diff --git a/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/match3obs_special_0.png.meta b/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/match3obs_special_0.png.meta new file mode 100644 index 0000000000000000000000000000000000000000..880d53c638409fa0396950141f9e9974bdbbc555 Binary files /dev/null and b/com.unity.ml-agents.tests/Tests/Editor/Integrations/Match3/match3obs_special_0.png.meta differ diff --git a/com.unity.ml-agents/Runtime.meta b/com.unity.ml-agents/Runtime.meta new file mode 100644 index 0000000000000000000000000000000000000000..b5ab5034ab9e187823f2364f1fdd48fa68dd27f3 Binary files /dev/null and b/com.unity.ml-agents/Runtime.meta differ diff --git a/com.unity.ml-agents/Runtime/Actuators.meta b/com.unity.ml-agents/Runtime/Actuators.meta new file mode 100644 index 0000000000000000000000000000000000000000..96bbfb99b3a8aea380baa89b290a6a19754d81d9 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Actuators.meta differ diff --git a/com.unity.ml-agents/Runtime/Actuators/IBuiltInActuator.cs b/com.unity.ml-agents/Runtime/Actuators/IBuiltInActuator.cs new file mode 100644 index 0000000000000000000000000000000000000000..2594b7768971cd2b817fe0a7bb78c95112b59657 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Actuators/IBuiltInActuator.cs @@ -0,0 +1,49 @@ +namespace Unity.MLAgents.Actuators +{ + /// + /// Identifiers for "built in" actuator types. + /// These are only used for analytics, and should not be used for any runtime decisions. + /// + /// NOTE: Do not renumber these, since the values are used for analytics. Renaming is allowed though. + /// + public enum BuiltInActuatorType + { + /// + /// Default Sensor type if it cannot be determined. + /// + Unknown = 0, + + /// + /// VectorActuator used by the Agent + /// + AgentVectorActuator = 1, + + /// + /// Corresponds to + /// + VectorActuator = 2, + + /// + /// Corresponds to the Match3Actuator. + /// + Match3Actuator = 3, + + /// + /// Corresponds to the InputActionActuator. + /// + InputActionActuator = 4, + } + + /// + /// Interface for actuators that are provided as part of ML-Agents. + /// User-implemented actuators don't need to use this interface. + /// + internal interface IBuiltInActuator + { + /// + /// Return the corresponding BuiltInActuatorType for the actuator. + /// + /// A BuiltInActuatorType corresponding to the actuator. + BuiltInActuatorType GetBuiltInActuatorType(); + } +} diff --git a/com.unity.ml-agents/Runtime/Actuators/IBuiltInActuator.cs.meta b/com.unity.ml-agents/Runtime/Actuators/IBuiltInActuator.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..da1d96f271cd7d00412156c95bfe17cf5625143a Binary files /dev/null and b/com.unity.ml-agents/Runtime/Actuators/IBuiltInActuator.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Actuators/IDiscreteActionMask.cs b/com.unity.ml-agents/Runtime/Actuators/IDiscreteActionMask.cs new file mode 100644 index 0000000000000000000000000000000000000000..995a8a574a2f95c189135c1961d04c3f4bb94cda --- /dev/null +++ b/com.unity.ml-agents/Runtime/Actuators/IDiscreteActionMask.cs @@ -0,0 +1,26 @@ +namespace Unity.MLAgents.Actuators +{ + /// + /// Interface for writing a mask to disable discrete actions for agents for the next decision. + /// + public interface IDiscreteActionMask + { + /// + /// Set whether or not the action index for the given branch is allowed. + /// + /// + /// By default, all discrete actions are allowed. + /// If isEnabled is false, the agent will not be able to perform the actions passed as argument + /// at the next decision for the specified action branch. The actionIndex corresponds + /// to the action options the agent will be unable to perform. + /// + /// See [Agents - Actions] for more information on masking actions. + /// + /// [Agents - Actions]: https://docs.unity3d.com/Packages/com.unity.ml-agents@latest/index.html?subfolder=/manual/Learning-Environment-Design-Agents.html#masking-discrete-actions + /// + /// The branch for which the actions will be masked. + /// Index of the action. + /// Whether the action is allowed or not. + void SetActionEnabled(int branch, int actionIndex, bool isEnabled); + } +} diff --git a/com.unity.ml-agents/Runtime/Actuators/IDiscreteActionMask.cs.meta b/com.unity.ml-agents/Runtime/Actuators/IDiscreteActionMask.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..ebfa10158fcb789feb1ecb814a487f42da026176 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Actuators/IDiscreteActionMask.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Actuators/IHeuristicProvider.cs b/com.unity.ml-agents/Runtime/Actuators/IHeuristicProvider.cs new file mode 100644 index 0000000000000000000000000000000000000000..b992361c8387498bdc18ca8c40346ff4c450391a --- /dev/null +++ b/com.unity.ml-agents/Runtime/Actuators/IHeuristicProvider.cs @@ -0,0 +1,18 @@ +namespace Unity.MLAgents.Actuators +{ + /// + /// Interface that allows objects to fill out an data structure for controlling + /// behavior of Agents or Actuators. + /// + public interface IHeuristicProvider + { + /// + /// Method called on objects which are expected to fill out the data structure. + /// Object that implement this interface should be careful to be consistent in the placement of their actions + /// in the data structure. + /// + /// The data structure to be filled by the + /// object implementing this interface. + void Heuristic(in ActionBuffers actionBuffersOut); + } +} diff --git a/com.unity.ml-agents/Runtime/Actuators/IHeuristicProvider.cs.meta b/com.unity.ml-agents/Runtime/Actuators/IHeuristicProvider.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..ca8338a0720edf0ca39e417fb9bae44e55c6c874 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Actuators/IHeuristicProvider.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Actuators/VectorActuator.cs b/com.unity.ml-agents/Runtime/Actuators/VectorActuator.cs new file mode 100644 index 0000000000000000000000000000000000000000..586058aad39da30e516dd174f97839e6f165e67c --- /dev/null +++ b/com.unity.ml-agents/Runtime/Actuators/VectorActuator.cs @@ -0,0 +1,105 @@ +using UnityEngine.Profiling; + +namespace Unity.MLAgents.Actuators +{ + /// + /// IActuator implementation that forwards calls to an and an . + /// + internal class VectorActuator : IActuator, IBuiltInActuator + { + IActionReceiver m_ActionReceiver; + IHeuristicProvider m_HeuristicProvider; + + ActionBuffers m_ActionBuffers; + internal ActionBuffers ActionBuffers + { + get => m_ActionBuffers; + private set => m_ActionBuffers = value; + } + + /// + /// Create a VectorActuator that forwards to the provided IActionReceiver. + /// + /// The used for OnActionReceived and WriteDiscreteActionMask. + /// If this parameter also implements it will be cast and used to forward calls to + /// . + /// + /// + public VectorActuator(IActionReceiver actionReceiver, + ActionSpec actionSpec, + string name = "VectorActuator") + : this(actionReceiver, actionReceiver as IHeuristicProvider, actionSpec, name) { } + + /// + /// Create a VectorActuator that forwards to the provided IActionReceiver. + /// + /// The used for OnActionReceived and WriteDiscreteActionMask. + /// The used to fill the + /// for Heuristic Policies. + /// + /// + public VectorActuator(IActionReceiver actionReceiver, + IHeuristicProvider heuristicProvider, + ActionSpec actionSpec, + string name = "VectorActuator") + { + m_ActionReceiver = actionReceiver; + m_HeuristicProvider = heuristicProvider; + ActionSpec = actionSpec; + string suffix; + if (actionSpec.NumContinuousActions == 0) + { + suffix = "-Discrete"; + } + else if (actionSpec.NumDiscreteActions == 0) + { + suffix = "-Continuous"; + } + else + { + suffix = $"-Continuous-{actionSpec.NumContinuousActions}-Discrete-{actionSpec.NumDiscreteActions}"; + } + Name = name + suffix; + } + + /// + public void ResetData() + { + m_ActionBuffers = ActionBuffers.Empty; + } + + /// + public void OnActionReceived(ActionBuffers actionBuffers) + { + Profiler.BeginSample("VectorActuator.OnActionReceived"); + m_ActionBuffers = actionBuffers; + m_ActionReceiver.OnActionReceived(m_ActionBuffers); + Profiler.EndSample(); + } + + public void Heuristic(in ActionBuffers actionBuffersOut) + { + Profiler.BeginSample("VectorActuator.Heuristic"); + m_HeuristicProvider?.Heuristic(actionBuffersOut); + Profiler.EndSample(); + } + + /// + public void WriteDiscreteActionMask(IDiscreteActionMask actionMask) + { + m_ActionReceiver.WriteDiscreteActionMask(actionMask); + } + + /// + public ActionSpec ActionSpec { get; } + + /// + public string Name { get; } + + /// + public virtual BuiltInActuatorType GetBuiltInActuatorType() + { + return BuiltInActuatorType.VectorActuator; + } + } +} diff --git a/com.unity.ml-agents/Runtime/Actuators/VectorActuator.cs.meta b/com.unity.ml-agents/Runtime/Actuators/VectorActuator.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..6e9f68b913b55433c4b25338c5bd8d732581ce1f Binary files /dev/null and b/com.unity.ml-agents/Runtime/Actuators/VectorActuator.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Agent.cs b/com.unity.ml-agents/Runtime/Agent.cs new file mode 100644 index 0000000000000000000000000000000000000000..fbec5722ec336dae1beb0693b419c66f3831a2a4 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Agent.cs @@ -0,0 +1,1436 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using UnityEngine; +using Unity.InferenceEngine; +using Unity.MLAgents.Actuators; +using Unity.MLAgents.Sensors; +using Unity.MLAgents.Sensors.Reflection; +using Unity.MLAgents.Demonstrations; +using Unity.MLAgents.Policies; +using UnityEngine.Serialization; + +namespace Unity.MLAgents +{ + /// + /// Struct that contains all the information for an Agent, including its + /// observations, actions and current status. + /// + public struct AgentInfo + { + /// + /// Keeps track of the last actions taken by the Brain. + /// + public ActionBuffers storedActions; + + /// + /// For discrete control, specifies the actions that the agent cannot take. + /// An element of the mask array is true if the action is prohibited. + /// + public bool[] discreteActionMasks; + + /// + /// The current agent reward. + /// + public float reward; + + /// + /// The current group reward received by the agent. + /// + public float groupReward; + + /// + /// Whether the agent is done or not. + /// + public bool done; + + /// + /// Whether the agent has reached its max step count for this episode. + /// + public bool maxStepReached; + + /// + /// Episode identifier each agent receives at every reset. It is used + /// to separate between different agents in the environment. + /// + public int episodeId; + + /// + /// MultiAgentGroup identifier. + /// + public int groupId; + + /// + /// Clear stored actions. + /// + public void ClearActions() + { + storedActions.Clear(); + } + + /// + /// Copy actions. + /// + /// The ActionBuffers to copy from. + public void CopyActions(ActionBuffers actionBuffers) + { + var continuousActions = storedActions.ContinuousActions; + for (var i = 0; i < actionBuffers.ContinuousActions.Length; i++) + { + continuousActions[i] = actionBuffers.ContinuousActions[i]; + } + var discreteActions = storedActions.DiscreteActions; + for (var i = 0; i < actionBuffers.DiscreteActions.Length; i++) + { + discreteActions[i] = actionBuffers.DiscreteActions[i]; + } + } + } + + /// + /// Simple wrapper around VectorActuator that overrides GetBuiltInActuatorType + /// so that it can be distinguished from a standard VectorActuator. + /// + internal class AgentVectorActuator : VectorActuator + { + public AgentVectorActuator(IActionReceiver actionReceiver, + IHeuristicProvider heuristicProvider, + ActionSpec actionSpec, + string name = "VectorActuator" + ) : base(actionReceiver, heuristicProvider, actionSpec, name) + { } + + public override BuiltInActuatorType GetBuiltInActuatorType() + { + return BuiltInActuatorType.AgentVectorActuator; + } + } + + /// + /// An agent is an actor that can observe its environment, decide on the + /// best course of action using those observations, and execute those actions + /// within the environment. + /// + /// + /// Use the Agent class as the subclass for implementing your own agents. Add + /// your Agent implementation to a [GameObject] in the [Unity scene] that serves + /// as the agent's environment. + /// + /// Agents in an environment operate in *steps*. At each step, an agent collects observations, + /// passes them to its decision-making policy, and receives an action vector in response. + /// + /// Agents make observations using implementations. The ML-Agents + /// API provides implementations for visual observations () + /// raycast observations (), and arbitrary + /// data observations (). You can add the + /// and or + /// components to an agent's [GameObject] to use + /// those sensor types. You can implement the + /// function in your Agent subclass to use a vector observation. The Agent class calls this + /// function before it uses the observation vector to make a decision. (If you only use + /// visual or raycast observations, you do not need to implement + /// .) + /// + /// Assign a decision making policy to an agent using a + /// component attached to the agent's [GameObject]. The setting + /// determines how decisions are made: + /// + /// : decisions are made by the external process, + /// when connected. Otherwise, decisions are made using inference. If no inference model + /// is specified in the BehaviorParameters component, then heuristic decision + /// making is used. + /// : decisions are always made using the trained + /// model specified in the component. + /// : when a decision is needed, the agent's + /// function is called. Your implementation is responsible for + /// providing the appropriate action. + /// + /// To trigger an agent decision automatically, you can attach a + /// component to the Agent game object. You can also call the agent's + /// function manually. You only need to call when the agent is + /// in a position to act upon the decision. In many cases, this will be every [FixedUpdate] + /// callback, but could be less frequent. For example, an agent that hops around its environment + /// can only take an action when it touches the ground, so several frames might elapse between + /// one decision and the need for the next. + /// + /// Use the function to implement the actions your agent can take, + /// such as moving to reach a goal or interacting with its environment. + /// + /// When you call on an agent or the agent reaches its count, + /// its current episode ends. You can reset the agent -- or remove it from the + /// environment -- by implementing the function. An agent also + /// becomes done when the resets the environment, which only happens when + /// the receives a reset signal from an external process via the + /// . + /// + /// The Agent class extends the Unity [MonoBehaviour] class. You can implement the + /// standard [MonoBehaviour] functions as needed for your agent. Since an agent's + /// observations and actions typically take place during the [FixedUpdate] phase, you should + /// only use the [MonoBehaviour.Update] function for cosmetic purposes. If you override the [MonoBehaviour] + /// methods, [OnEnable()] or [OnDisable()], always call the base Agent class implementations. + /// + /// You can implement the function to specify agent actions using + /// your own heuristic algorithm. Implementing a heuristic function can be useful + /// for debugging. For example, you can use keyboard input to select agent actions in + /// order to manually control an agent's behavior. + /// + /// Note that you can change the inference model assigned to an agent at any step + /// by calling . + /// + /// See [Agents] and [Reinforcement Learning in Unity] in the [Unity ML-Agents Toolkit manual] for + /// more information on creating and training agents. + /// + /// For sample implementations of agent behavior, see the examples available in the + /// [Unity ML-Agents Toolkit] on Github. + /// + /// [MonoBehaviour]: https://docs.unity3d.com/ScriptReference/MonoBehaviour.html + /// [GameObject]: https://docs.unity3d.com/Manual/GameObjects.html + /// [Unity scene]: https://docs.unity3d.com/Manual/CreatingScenes.html + /// [FixedUpdate]: https://docs.unity3d.com/ScriptReference/MonoBehaviour.FixedUpdate.html + /// [MonoBehaviour.Update]: https://docs.unity3d.com/ScriptReference/MonoBehaviour.Update.html + /// [OnEnable()]: https://docs.unity3d.com/ScriptReference/MonoBehaviour.OnEnable.html + /// [OnDisable()]: https://docs.unity3d.com/ScriptReference/MonoBehaviour.OnDisable.html] + /// [OnBeforeSerialize()]: https://docs.unity3d.com/ScriptReference/MonoBehaviour.OnBeforeSerialize.html + /// [OnAfterSerialize()]: https://docs.unity3d.com/ScriptReference/MonoBehaviour.OnAfterSerialize.html + /// [Agents]: https://docs.unity3d.com/Packages/com.unity.ml-agents@latest/index.html?subfolder=/manual/Learning-Environment-Design-Agents.html + /// [Reinforcement Learning in Unity]: https://docs.unity3d.com/Packages/com.unity.ml-agents@latest/index.html?subfolder=/manual/Learning-Environment-Design.html + /// [Unity ML-Agents Toolkit]: https://github.com/Unity-Technologies/ml-agents + /// [Unity ML-Agents Toolkit manual]: https://docs.unity3d.com/Packages/com.unity.ml-agents@latest + /// + /// + [HelpURL("https://docs.unity3d.com/Packages/com.unity.ml-agents@latest/index.html?subfolder=/manual/" + + "Learning-Environment-Design-Agents.html")] + [Serializable] + [RequireComponent(typeof(BehaviorParameters))] + [DefaultExecutionOrder(-50)] + public partial class Agent : MonoBehaviour, ISerializationCallbackReceiver, IActionReceiver, IHeuristicProvider + { + IPolicy m_Brain; + BehaviorParameters m_PolicyFactory; + + /// This code is here to make the upgrade path for users using MaxStep + /// easier. We will hook into the Serialization code and make sure that + /// agentParameters.maxStep and this.maxStep are in sync. + [Serializable] + internal struct AgentParameters + { + public int maxStep; + } + + [SerializeField] + [HideInInspector] + internal AgentParameters agentParameters; + [SerializeField] + [HideInInspector] + internal bool hasUpgradedFromAgentParameters; + + /// + /// The maximum number of steps the agent takes before being done. + /// + /// The maximum steps for an agent to take before it resets; or 0 for + /// unlimited steps. + /// + /// The max step value determines the maximum length of an agent's episodes. + /// Set to a positive integer to limit the episode length to that many steps. + /// Set to 0 for unlimited episode length. + /// + /// When an episode ends and a new one begins, the Agent object's + /// function is called. You can implement + /// to reset the agent or remove it from the + /// environment. An agent's episode can also end if you call its + /// method or an external process resets the environment through the . + /// + /// Consider limiting the number of steps in an episode to avoid wasting time during + /// training. If you set the max step value to a reasonable estimate of the time it should + /// take to complete a task, then agents that haven’t succeeded in that time frame will + /// reset and start a new training episode rather than continue to fail. + /// + /// **Note:** in general, you should limit the differences between the code you execute + /// during training and the code you run during inference. + /// + /// + /// + /// To use a step limit when training while allowing agents to run without resetting + /// outside of training, you can set the max step to 0 in + /// if the is not connected to an external process. + /// + /// + /// using Unity.MLAgents; + /// + /// public class MyAgent : Agent + /// { + /// public override void Initialize() + /// { + /// if (!Academy.Instance.IsCommunicatorOn) + /// { + /// this.MaxStep = 0; + /// } + /// } + /// } + /// + /// + [FormerlySerializedAs("maxStep")] + [HideInInspector] public int MaxStep; + + /// Current Agent information (message sent to Brain). + AgentInfo m_Info; + + /// Represents the reward the agent accumulated during the current step. + /// It is reset to 0 at the beginning of every step. + /// Should be set to a positive value when the agent performs a "good" + /// action that we wish to reinforce/reward, and set to a negative value + /// when the agent performs a "bad" action that we wish to punish/deter. + /// Additionally, the magnitude of the reward should not exceed 1.0 + float m_Reward; + + /// Represents the group reward the agent accumulated during the current step. + float m_GroupReward; + + /// Keeps track of the cumulative reward in this episode. + float m_CumulativeReward; + + /// Whether or not the agent requests an action. + bool m_RequestAction; + + /// Whether or not the agent requests a decision. + bool m_RequestDecision; + + /// Keeps track of the number of steps taken by the agent in this episode. + /// Note that this value is different for each agent, and may not overlap + /// with the step counter in the Academy, since agents reset based on + /// their own experience. + int m_StepCount; + + /// Number of times the Agent has completed an episode. + int m_CompletedEpisodes; + + /// Episode identifier each agent receives. It is used + /// to separate between different agents in the environment. + /// This Id will be changed every time the Agent resets. + int m_EpisodeId; + + /// Whether or not the Agent has been initialized already + bool m_Initialized; + + /// + /// Set of DemonstrationWriters that the Agent will write its step information to. + /// If you use a DemonstrationRecorder component, this will automatically register its DemonstrationWriter. + /// You can also add your own DemonstrationWriter by calling + /// DemonstrationRecorder.AddDemonstrationWriterToAgent() + /// + internal ISet DemonstrationWriters = new HashSet(); + + /// + /// List of sensors used to generate observations. + /// Currently generated from attached SensorComponents, and a legacy VectorSensor + /// + internal List sensors; + + /// + /// VectorSensor which is written to by AddVectorObs + /// + internal VectorSensor collectObservationsSensor; + + /// + /// StackingSensor which is written to by AddVectorObs + /// + internal StackingSensor stackedCollectObservationsSensor; + + private RecursionChecker m_CollectObservationsChecker = new RecursionChecker("CollectObservations"); + private RecursionChecker m_OnEpisodeBeginChecker = new RecursionChecker("OnEpisodeBegin"); + + /// + /// List of IActuators that this Agent will delegate actions to if any exist. + /// + ActuatorManager m_ActuatorManager; + + /// + /// VectorActuator which is used by default if no other sensors exist on this Agent. This VectorSensor will + /// delegate its actions to by default in order to keep backward compatibility + /// with the current behavior of Agent. + /// + IActuator m_VectorActuator; + + /// Currect MultiAgentGroup ID. Default to 0 (meaning no group) + int m_GroupId; + + /// Delegate for the agent to unregister itself from the MultiAgentGroup without cyclic reference + /// between agent and the group + internal event Action OnAgentDisabled; + + /// + /// Called when the Agent is being loaded (before OnEnable()). + /// + /// + /// This function registers the RpcCommunicator delegate if no delegate has been registered with CommunicatorFactory. + /// Always call the base Agent class version of this function if you implement `Awake()` in your + /// own Agent subclasses. + /// + /// + /// + /// protected override void Awake() + /// { + /// base.Awake(); + /// // additional Awake logic... + /// } + /// + /// + protected internal virtual void Awake() + { +#if UNITY_EDITOR || UNITY_STANDALONE + if (!CommunicatorFactory.CommunicatorRegistered) + { + Debug.Log("Registered Communicator in Agent."); + CommunicatorFactory.Register(RpcCommunicator.Create); + } +#endif + } + + /// + /// Called when the attached [GameObject] becomes enabled and active. + /// [GameObject]: https://docs.unity3d.com/Manual/GameObjects.html + /// + /// + /// This function initializes the Agent instance, if it hasn't been initialized yet. + /// Always call the base Agent class version of this function if you implement `OnEnable()` + /// in your own Agent subclasses. + /// + /// + /// + /// protected override void OnEnable() + /// { + /// base.OnEnable(); + /// // additional OnEnable logic... + /// } + /// + /// + protected virtual void OnEnable() + { + LazyInitialize(); + } + + /// + /// Called by Unity immediately before serializing this object. + /// + /// + /// The Agent class uses OnBeforeSerialize() for internal housekeeping. Call the + /// base class implementation if you need your own custom serialization logic. + /// + /// See [OnBeforeSerialize] for more information. + /// + /// [OnBeforeSerialize]: https://docs.unity3d.com/ScriptReference/ISerializationCallbackReceiver.OnAfterDeserialize.html + /// + /// + /// + /// public new void OnBeforeSerialize() + /// { + /// base.OnBeforeSerialize(); + /// // additional serialization logic... + /// } + /// + /// + public void OnBeforeSerialize() + { + // Manages a serialization upgrade issue from v0.13 to v0.14 where MaxStep moved + // from AgentParameters (since removed) to Agent + if (MaxStep == 0 && MaxStep != agentParameters.maxStep && !hasUpgradedFromAgentParameters) + { + MaxStep = agentParameters.maxStep; + } + hasUpgradedFromAgentParameters = true; + } + + /// + /// Called by Unity immediately after deserializing this object. + /// + /// + /// The Agent class uses OnAfterDeserialize() for internal housekeeping. Call the + /// base class implementation if you need your own custom deserialization logic. + /// + /// See [OnAfterDeserialize] for more information. + /// + /// [OnAfterDeserialize]: https://docs.unity3d.com/ScriptReference/ISerializationCallbackReceiver.OnAfterDeserialize.html + /// + /// + /// + /// public new void OnAfterDeserialize() + /// { + /// base.OnAfterDeserialize(); + /// // additional deserialization logic... + /// } + /// + /// + public void OnAfterDeserialize() + { + // Manages a serialization upgrade issue from v0.13 to v0.14 where MaxStep moved + // from AgentParameters (since removed) to Agent + if (MaxStep == 0 && MaxStep != agentParameters.maxStep && !hasUpgradedFromAgentParameters) + { + MaxStep = agentParameters.maxStep; + } + hasUpgradedFromAgentParameters = true; + } + + /// + /// Initializes the agent. Can be safely called multiple times. + /// + /// + /// This function calls your implementation, if one exists. + /// + public void LazyInitialize() + { + if (m_Initialized) + { + return; + } + m_Initialized = true; + + // Grab the "static" properties for the Agent. + m_EpisodeId = EpisodeIdCounter.GetEpisodeId(); + m_PolicyFactory = GetComponent(); + + m_Info = new AgentInfo(); + sensors = new List(); + + Academy.Instance.AgentIncrementStep += AgentIncrementStep; + Academy.Instance.AgentSendState += SendInfo; + Academy.Instance.DecideAction += DecideAction; + Academy.Instance.AgentAct += AgentStep; + Academy.Instance.AgentForceReset += _AgentReset; + + using (TimerStack.Instance.Scoped("InitializeActuators")) + { + InitializeActuators(); + } + + m_Brain = m_PolicyFactory.GeneratePolicy(m_ActuatorManager.GetCombinedActionSpec(), m_ActuatorManager); + ResetData(); + Initialize(); + + using (TimerStack.Instance.Scoped("InitializeSensors")) + { + InitializeSensors(); + } + + m_Info.storedActions = new ActionBuffers( + new float[m_ActuatorManager.NumContinuousActions], + new int[m_ActuatorManager.NumDiscreteActions] + ); + + m_Info.groupId = m_GroupId; + + // The first time the Academy resets, all Agents in the scene will be + // forced to reset through the event. + // To avoid the Agent resetting twice, the Agents will not begin their + // episode when initializing until after the Academy had its first reset. + if (Academy.Instance.TotalStepCount != 0) + { + using (m_OnEpisodeBeginChecker.Start()) + { + OnEpisodeBegin(); + } + } + } + + /// + /// The reason that the Agent has been set to "done". + /// + enum DoneReason + { + /// + /// The episode was ended manually by calling . + /// + DoneCalled, + + /// + /// The max steps for the Agent were reached. + /// + MaxStepReached, + + /// + /// The Agent was disabled. + /// + Disabled, + } + + /// + /// Called when the attached [GameObject] becomes disabled and inactive. + /// [GameObject]: https://docs.unity3d.com/Manual/GameObjects.html + /// + /// + /// Always call the base Agent class version of this function if you implement `OnDisable()` + /// in your own Agent subclasses. + /// + /// + /// + /// protected override void OnDisable() + /// { + /// base.OnDisable(); + /// // additional OnDisable logic... + /// } + /// + /// + /// + protected virtual void OnDisable() + { + DemonstrationWriters.Clear(); + + // If Academy.Dispose has already been called, we don't need to unregister with it. + // We don't want to even try, because this will lazily create a new Academy! + if (Academy.IsInitialized) + { + Academy.Instance.AgentIncrementStep -= AgentIncrementStep; + Academy.Instance.AgentSendState -= SendInfo; + Academy.Instance.DecideAction -= DecideAction; + Academy.Instance.AgentAct -= AgentStep; + Academy.Instance.AgentForceReset -= _AgentReset; + NotifyAgentDone(DoneReason.Disabled); + } + + CleanupSensors(); + m_Brain?.Dispose(); + OnAgentDisabled?.Invoke(this); + m_Initialized = false; + } + + void NotifyAgentDone(DoneReason doneReason) + { + if (m_Info.done) + { + // The Agent was already marked as Done and should not be notified again + return; + } + m_Info.episodeId = m_EpisodeId; + m_Info.reward = m_Reward; + m_Info.groupReward = m_GroupReward; + m_Info.done = true; + m_Info.maxStepReached = doneReason == DoneReason.MaxStepReached; + m_Info.groupId = m_GroupId; + UpdateSensors(); + // Make sure the latest observations are being passed to training. + using (m_CollectObservationsChecker.Start()) + { + CollectObservations(collectObservationsSensor); + } + // Request the last decision with no callbacks + // We request a decision so Python knows the Agent is done immediately + m_Brain?.RequestDecision(m_Info, sensors); + + // We also have to write any to any DemonstationStores so that they get the "done" flag. + if (DemonstrationWriters.Count != 0) + { + foreach (var demoWriter in DemonstrationWriters) + { + demoWriter.Record(m_Info, sensors); + } + } + + ResetSensors(); + + if (doneReason != DoneReason.Disabled) + { + // We don't want to update the reward stats when the Agent is disabled, because this will make + // the rewards look lower than they actually are during shutdown. + m_CompletedEpisodes++; + UpdateRewardStats(); + } + + m_Reward = 0f; + m_GroupReward = 0f; + m_CumulativeReward = 0f; + m_RequestAction = false; + m_RequestDecision = false; + m_Info.storedActions.Clear(); + } + + /// + /// Updates the Model assigned to this Agent instance. + /// + /// + /// If the agent already has an assigned model, that model is replaced with the + /// the provided one. However, if you call this function with arguments that are + /// identical to the current parameters of the agent, then no changes are made. + /// + /// **Note:** the parameter is ignored when not training. + /// The and parameters + /// are ignored when not using inference. + /// + /// The identifier of the behavior. This + /// will categorize the agent when training. + /// + /// The model to use for inference. + /// Define the device on which the model + /// will be run. + public void SetModel( + string behaviorName, + ModelAsset model, + InferenceDevice inferenceDevice = InferenceDevice.Default) + { + if (behaviorName == m_PolicyFactory.BehaviorName && + model == m_PolicyFactory.Model && + inferenceDevice == m_PolicyFactory.InferenceDevice) + { + // If everything is the same, don't make any changes. + return; + } + NotifyAgentDone(DoneReason.Disabled); + m_PolicyFactory.Model = model; + m_PolicyFactory.InferenceDevice = inferenceDevice; + m_PolicyFactory.BehaviorName = behaviorName; + ReloadPolicy(); + } + + internal void ReloadPolicy() + { + if (!m_Initialized) + { + // If we haven't initialized yet, no need to make any changes now; they'll + // happen in LazyInitialize later. + return; + } + m_Brain?.Dispose(); + m_Brain = m_PolicyFactory.GeneratePolicy(m_ActuatorManager.GetCombinedActionSpec(), m_ActuatorManager); + } + + /// + /// Returns the current step counter (within the current episode). + /// + /// The current step count. + public int StepCount + { + get { return m_StepCount; } + } + + /// + /// Returns the number of episodes that the Agent has completed (either + /// was called, or maxSteps was reached). + /// + public int CompletedEpisodes + { + get { return m_CompletedEpisodes; } + } + + /// + /// Overrides the current step reward of the agent and updates the episode + /// reward accordingly. + /// + /// + /// This function replaces any rewards given to the agent during the current step. + /// Use to incrementally change the reward rather than + /// overriding it. + /// + /// Typically, you assign rewards in the Agent subclass's + /// implementation after carrying out the received action and evaluating its success. + /// + /// Rewards are used during reinforcement learning; they are ignored during inference. + /// + /// See [Agents - Rewards] for general advice on implementing rewards and [Reward Signals] + /// for information about mixing reward signals from curiosity and Generative Adversarial + /// Imitation Learning (GAIL) with rewards supplied through this method. + /// + /// [Agents - Rewards]: https://docs.unity3d.com/Packages/com.unity.ml-agents@latest/index.html?subfolder=/manual/Learning-Environment-Design-Agents.html#rewards + /// [Reward Signals]: https://docs.unity3d.com/Packages/com.unity.ml-agents@latest/index.html?subfolder=/manual/ML-Agents-Overview.html#a-quick-note-on-reward-signals + /// + /// The new value of the reward. + public void SetReward(float reward) + { + Utilities.DebugCheckNanAndInfinity(reward, nameof(reward), nameof(SetReward)); + m_CumulativeReward += (reward - m_Reward); + m_Reward = reward; + } + + /// + /// Increments the step and episode rewards by the provided value. + /// + /// Use a positive reward to reinforce desired behavior. You can use a + /// negative reward to penalize mistakes. Use to + /// set the reward assigned to the current step with a specific value rather than + /// increasing or decreasing it. + /// + /// Typically, you assign rewards in the Agent subclass's + /// implementation after carrying out the received action and evaluating its success. + /// + /// Rewards are used during reinforcement learning; they are ignored during inference. + /// + /// See [Agents - Rewards] for general advice on implementing rewards and [Reward Signals] + /// for information about mixing reward signals from curiosity and Generative Adversarial + /// Imitation Learning (GAIL) with rewards supplied through this method. + /// + /// [Agents - Rewards]: https://docs.unity3d.com/Packages/com.unity.ml-agents@latest/index.html?subfolder=/manual/Learning-Environment-Design-Agents.html#rewards + /// [Reward Signals]: https://docs.unity3d.com/Packages/com.unity.ml-agents@latest/index.html?subfolder=/manual/ML-Agents-Overview.html#a-quick-note-on-reward-signals + /// + /// Incremental reward value. + public void AddReward(float increment) + { + Utilities.DebugCheckNanAndInfinity(increment, nameof(increment), nameof(AddReward)); + m_Reward += increment; + m_CumulativeReward += increment; + } + + internal void SetGroupReward(float reward) + { + Utilities.DebugCheckNanAndInfinity(reward, nameof(reward), nameof(SetGroupReward)); + m_GroupReward = reward; + } + + internal void AddGroupReward(float increment) + { + Utilities.DebugCheckNanAndInfinity(increment, nameof(increment), nameof(AddGroupReward)); + m_GroupReward += increment; + } + + /// + /// Retrieves the episode reward for the Agent. + /// + /// The episode reward. + public float GetCumulativeReward() + { + return m_CumulativeReward; + } + + void UpdateRewardStats() + { + var gaugeName = $"{m_PolicyFactory.BehaviorName}.CumulativeReward"; + TimerStack.Instance.SetGauge(gaugeName, GetCumulativeReward()); + } + + /// + /// Sets the done flag to true and resets the agent. + /// + /// + /// This should be used when the episode can no longer continue, such as when the Agent + /// reaches the goal or fails at the task. + /// + /// + /// + public void EndEpisode() + { + EndEpisodeAndReset(DoneReason.DoneCalled); + } + + /// + /// Indicate that the episode is over but not due to the "fault" of the Agent. + /// This has the same end result as calling , but has a + /// slightly different effect on training. + /// + /// + /// This should be used when the episode could continue, but has gone on for + /// a sufficient number of steps. + /// + /// + /// + public void EpisodeInterrupted() + { + EndEpisodeAndReset(DoneReason.MaxStepReached); + } + + /// + /// Internal method to end the episode and reset the Agent. + /// + /// reason to end the episode. + void EndEpisodeAndReset(DoneReason reason) + { + NotifyAgentDone(reason); + _AgentReset(); + } + + /// + /// Requests a new decision for this agent. + /// + /// + /// Call `RequestDecision()` whenever an agent needs a decision. You often + /// want to request a decision every environment step. However, if an agent + /// cannot use the decision every step, then you can request a decision less + /// frequently. + /// + /// You can add a component to the agent's + /// [GameObject] to drive the agent's decision making. When you use this component, + /// do not call `RequestDecision()` separately. + /// + /// Note that this function calls ; you do not need to + /// call both functions at the same time. + /// + /// [GameObject]: https://docs.unity3d.com/Manual/GameObjects.html + /// + public void RequestDecision() + { + m_RequestDecision = true; + RequestAction(); + } + + /// + /// Requests an action for this agent. + /// + /// + /// Call `RequestAction()` to repeat the previous action returned by the agent's + /// most recent decision. A new decision is not requested. When you call this function, + /// the Agent instance invokes with the + /// existing action vector. + /// + /// You can use `RequestAction()` in situations where an agent must take an action + /// every update, but doesn't need to make a decision as often. For example, an + /// agent that moves through its environment might need to apply an action to keep + /// moving, but only needs to make a decision to change course or speed occasionally. + /// + /// You can add a component to the agent's + /// [GameObject] to drive the agent's decision making and action frequency. When you + /// use this component, do not call `RequestAction()` separately. + /// + /// Note that calls `RequestAction()`; you do not need to + /// call both functions at the same time. + /// + /// [GameObject]: https://docs.unity3d.com/Manual/GameObjects.html + /// + public void RequestAction() + { + m_RequestAction = true; + } + + /// Helper function that resets all the data structures associated with + /// the agent. Typically used when the agent is being initialized or reset + /// at the end of an episode. + void ResetData() + { + m_ActuatorManager?.ResetData(); + } + + /// + /// Implement `Initialize()` to perform one-time initialization or set up of the + /// Agent instance. + /// + /// + /// `Initialize()` is called once when the agent is first enabled. If, for example, + /// the Agent object needs references to other [GameObjects] in the scene, you + /// can collect and store those references here. + /// + /// Note that is called at the start of each of + /// the agent's "episodes". You can use that function for items that need to be reset + /// for each episode. + /// + /// [GameObject]: https://docs.unity3d.com/Manual/GameObjects.html + /// + public virtual void Initialize() { } + + /// + /// Implement to choose an action for this agent using a custom heuristic. + /// + /// + /// Implement this function to provide custom decision making logic or to support manual + /// control of an agent using keyboard, mouse, game controller input, or a script. + /// + /// Your heuristic implementation can use any decision making logic you specify. Assign decision + /// values to the and + /// arrays , passed to your function as a parameter. + /// The same array will be reused between steps. It is up to the user to initialize + /// the values on each call, for example by calling `Array.Clear(actionsOut, 0, actionsOut.Length);`. + /// Add values to the array at the same indexes as they are used in your + /// function, which receives this array and + /// implements the corresponding agent behavior. See [Actions] for more information + /// about agent actions. + /// Note : Do not create a new float array of action in the `Heuristic()` method, + /// as this will prevent writing floats to the original action array. + /// + /// An agent calls this `Heuristic()` function to make a decision when you set its behavior + /// type to . The agent also calls this function if + /// you set its behavior type to when the + /// is not connected to an external training process and you do not + /// assign a trained model to the agent. + /// + /// To perform imitation learning, implement manual control of the agent in the `Heuristic()` + /// function so that you can record the demonstrations required for the imitation learning + /// algorithms. (Attach a [Demonstration Recorder] component to the agent's [GameObject] to + /// record the demonstration session to a file.) + /// + /// Even when you don’t plan to use heuristic decisions for an agent or imitation learning, + /// implementing a simple heuristic function can aid in debugging agent actions and interactions + /// with its environment. + /// + /// [Demonstration Recorder]: https://docs.unity3d.com/Packages/com.unity.ml-agents@latest/index.html?subfolder=/manual/Learning-Environment-Design-Agents.html#recording-demonstrations + /// [Actions]: https://docs.unity3d.com/Packages/com.unity.ml-agents@latest/index.html?subfolder=/manual/Learning-Environment-Design-Agents.html#actions + /// [GameObject]: https://docs.unity3d.com/Manual/GameObjects.html + /// + /// + /// + /// The following example illustrates a `Heuristic()` function that provides WASD-style + /// keyboard control for an agent that can move in two dimensions as well as jump. See + /// [Input Manager] for more information about the built-in Unity input functions. + /// You can also use the [Input System package], which provides a more flexible and + /// configurable input system. + /// [Input Manager]: https://docs.unity3d.com/Manual/class-InputManager.html + /// [Input System package]: https://docs.unity3d.com/Packages/com.unity.inputsystem@1.0/manual/index.html + /// + /// + /// public override void Heuristic(in ActionBuffers actionsOut) + /// { + /// var continuousActionsOut = actionsOut.ContinuousActions; + /// continuousActionsOut[0] = Input.GetAxis("Horizontal"); + /// continuousActionsOut[1] = Input.GetKey(KeyCode.Space) ? 1.0f : 0.0f; + /// continuousActionsOut[2] = Input.GetAxis("Vertical"); + /// } + /// + /// + /// The which contain the continuous and + /// discrete action buffers to write to. + /// + public virtual void Heuristic(in ActionBuffers actionsOut) + { + Debug.LogWarning("Heuristic method called but not implemented. Returning placeholder actions."); + } + + /// + /// Set up the list of ISensors on the Agent. By default, this will select any + /// SensorComponent's attached to the Agent. + /// + internal void InitializeSensors() + { + if (m_PolicyFactory == null) + { + m_PolicyFactory = GetComponent(); + } + if (m_PolicyFactory.ObservableAttributeHandling != ObservableAttributeOptions.Ignore) + { + var excludeInherited = + m_PolicyFactory.ObservableAttributeHandling == ObservableAttributeOptions.ExcludeInherited; + using (TimerStack.Instance.Scoped("CreateObservableSensors")) + { + var observableSensors = ObservableAttribute.CreateObservableSensors(this, excludeInherited); + sensors.AddRange(observableSensors); + } + } + + // Get all attached sensor components + SensorComponent[] attachedSensorComponents; + if (m_PolicyFactory.UseChildSensors) + { + attachedSensorComponents = GetComponentsInChildren(); + } + else + { + attachedSensorComponents = GetComponents(); + } + + sensors.Capacity += attachedSensorComponents.Length; + foreach (var component in attachedSensorComponents) + { + sensors.AddRange(component.CreateSensors()); + } + + // Support legacy CollectObservations + var param = m_PolicyFactory.BrainParameters; + if (param.VectorObservationSize > 0) + { + collectObservationsSensor = new VectorSensor(param.VectorObservationSize); + if (param.NumStackedVectorObservations > 1) + { + stackedCollectObservationsSensor = new StackingSensor( + collectObservationsSensor, param.NumStackedVectorObservations); + sensors.Add(stackedCollectObservationsSensor); + } + else + { + sensors.Add(collectObservationsSensor); + } + } + + // Sort the Sensors by name to ensure determinism + SensorUtils.SortSensors(sensors); + +#if DEBUG + // Make sure the names are actually unique + + for (var i = 0; i < sensors.Count - 1; i++) + { + Debug.Assert( + !sensors[i].GetName().Equals(sensors[i + 1].GetName()), + "Sensor names must be unique."); + } +#endif + } + + void CleanupSensors() + { + // Dispose all attached sensor + for (var i = 0; i < sensors.Count; i++) + { + var sensor = sensors[i]; + if (sensor is IDisposable disposableSensor) + { + disposableSensor.Dispose(); + } + } + } + + void InitializeActuators() + { + ActuatorComponent[] attachedActuators; + if (m_PolicyFactory.UseChildActuators) + { + attachedActuators = GetComponentsInChildren(); + } + else + { + attachedActuators = GetComponents(); + } + + // Support legacy OnActionReceived + // TODO don't set this up if the sizes are 0? + var param = m_PolicyFactory.BrainParameters; + m_VectorActuator = new AgentVectorActuator(this, this, param.ActionSpec); + m_ActuatorManager = new ActuatorManager(attachedActuators.Length + 1); + + m_ActuatorManager.Add(m_VectorActuator); + + foreach (var actuatorComponent in attachedActuators) + { + m_ActuatorManager.AddActuators(actuatorComponent.CreateActuators()); + } + } + + /// + /// Sends the Agent info to the linked Brain. + /// + void SendInfoToBrain() + { + if (!m_Initialized) + { + throw new UnityAgentsException("Call to SendInfoToBrain when Agent hasn't been initialized." + + "Please ensure that you are calling 'base.OnEnable()' if you have overridden OnEnable."); + } + + if (m_Brain == null) + { + return; + } + + if (m_Info.done) + { + m_Info.ClearActions(); + } + else + { + m_Info.CopyActions(m_ActuatorManager.StoredActions); + } + + UpdateSensors(); + using (TimerStack.Instance.Scoped("CollectObservations")) + { + using (m_CollectObservationsChecker.Start()) + { + CollectObservations(collectObservationsSensor); + } + } + using (TimerStack.Instance.Scoped("WriteActionMask")) + { + m_ActuatorManager.WriteActionMask(); + } + + m_Info.discreteActionMasks = m_ActuatorManager.DiscreteActionMask?.GetMask(); + m_Info.reward = m_Reward; + m_Info.groupReward = m_GroupReward; + m_Info.done = false; + m_Info.maxStepReached = false; + m_Info.episodeId = m_EpisodeId; + m_Info.groupId = m_GroupId; + + using (TimerStack.Instance.Scoped("RequestDecision")) + { + m_Brain.RequestDecision(m_Info, sensors); + } + + // If we have any DemonstrationWriters, write the AgentInfo and sensors to them. + if (DemonstrationWriters.Count != 0) + { + foreach (var demoWriter in DemonstrationWriters) + { + demoWriter.Record(m_Info, sensors); + } + } + } + + void UpdateSensors() + { + foreach (var sensor in sensors) + { + sensor.Update(); + } + } + + void ResetSensors() + { + foreach (var sensor in sensors) + { + sensor.Reset(); + } + } + + /// + /// Implement `CollectObservations()` to collect the vector observations of + /// the agent for the step. The agent observation describes the current + /// environment from the perspective of the agent. + /// + /// + /// The vector observations for the agent. + /// + /// + /// An agent's observation is any environment information that helps + /// the agent achieve its goal. For example, for a fighting agent, its + /// observation could include distances to friends or enemies, or the + /// current level of ammunition at its disposal. + /// + /// You can use a combination of vector, visual, and raycast observations for an + /// agent. If you only use visual or raycast observations, you do not need to + /// implement a `CollectObservations()` function. + /// + /// Add vector observations to the parameter passed to + /// this method by calling the helper methods: + /// - + /// - + /// - + /// - + /// - + /// - + /// - + /// - + /// + /// You can use any combination of these helper functions to build the agent's + /// vector of observations. You must build the vector in the same order + /// each time `CollectObservations()` is called and the length of the vector + /// must always be the same. In addition, the length of the observation must + /// match the + /// attribute of the linked Brain, which is set in the Editor on the + /// **Behavior Parameters** component attached to the agent's [GameObject]. + /// + /// For more information about observations, see [Observations and Sensors]. + /// + /// [GameObject]: https://docs.unity3d.com/Manual/GameObjects.html + /// [Observations and Sensors]: https://docs.unity3d.com/Packages/com.unity.ml-agents@latest/index.html?subfolder=/manual/Learning-Environment-Design-Agents.html#observations-and-sensors + /// + public virtual void CollectObservations(VectorSensor sensor) + { + } + + /// + /// Returns a read-only view of the observations that were generated in + /// . This is mainly useful inside of a + /// method to avoid recomputing the observations. + /// + /// A read-only view of the observations list. + public ReadOnlyCollection GetObservations() + { + return collectObservationsSensor.GetObservations(); + } + + /// + /// Returns a read-only view of the stacked observations that were generated in + /// . This is mainly useful inside of a + /// method to avoid recomputing the observations. + /// + /// A read-only view of the stacked observations list. + public ReadOnlyCollection GetStackedObservations() + { + return stackedCollectObservationsSensor.GetStackedObservations(); + } + + /// + /// Implement `WriteDiscreteActionMask()` to collects the masks for discrete + /// actions. When using discrete actions, the agent will not perform the masked + /// action. + /// + /// + /// The action mask for the agent. + /// + /// + /// When using Discrete Control, you can prevent the Agent from using a certain + /// action by masking it with . + /// + /// See [Agents - Actions] for more information on masking actions. + /// + /// [Agents - Actions]: https://docs.unity3d.com/Packages/com.unity.ml-agents@latest/index.html?subfolder=/manual/Learning-Environment-Design-Agents.html + /// + /// + public virtual void WriteDiscreteActionMask(IDiscreteActionMask actionMask) { } + + /// + /// Implement `OnActionReceived()` to specify agent behavior at every step, based + /// on the provided action. + /// + /// + /// + /// An action is passed to this function in the form of an . + /// Your implementation must use the array to direct the agent's behavior for the + /// current step. + /// + /// You decide how many elements you need in the ActionBuffers to control your + /// agent and what each element means. For example, if you want to apply a + /// force to move an agent around the environment, you can arbitrarily pick + /// three values in ActionBuffers.ContinuousActions array to use as the force components. + /// During training, the agent's policy learns to set those particular elements of + /// the array to maximize the training rewards the agent receives. (Of course, + /// if you implement a function, it must use the same + /// elements of the action array for the same purpose since there is no learning + /// involved.) + /// + /// An Agent can use continuous and/or discrete actions. Configure this along with the size + /// of the action array, in the of the agent's associated + /// component. + /// + /// When an agent uses continuous actions, the values in the ActionBuffers.ContinuousActions + /// array are floating point numbers. You should clamp the values to the range, + /// -1..1, to increase numerical stability during training. + /// + /// When an agent uses discrete actions, the values in the ActionBuffers.DiscreteActions array + /// are integers that each represent a specific, discrete action. For example, + /// you could define a set of discrete actions such as: + /// + /// + /// 0 = Do nothing + /// 1 = Move one space left + /// 2 = Move one space right + /// 3 = Move one space up + /// 4 = Move one space down + /// + /// + /// When making a decision, the agent picks one of the five actions and puts the + /// corresponding integer value in the ActionBuffers.DiscreteActions array. For example, if the agent + /// decided to move left, the ActionBuffers.DiscreteActions parameter would be an array with + /// a single element with the value 1. + /// + /// You can define multiple sets, or branches, of discrete actions to allow an + /// agent to perform simultaneous, independent actions. For example, you could + /// use one branch for movement and another branch for throwing a ball left, right, + /// up, or down, to allow the agent to do both in the same step. + /// + /// The ActionBuffers.DiscreteActions array of an agent with discrete actions contains one + /// element for each branch. The value of each element is the integer representing the + /// chosen action for that branch. The agent always chooses one action for each branch. + /// + /// When you use the discrete actions, you can prevent the training process + /// or the neural network model from choosing specific actions in a step by + /// implementing the + /// method. For example, if your agent is next to a wall, you could mask out any + /// actions that would result in the agent trying to move into the wall. + /// + /// For more information about implementing agent actions see [Agents - Actions]. + /// + /// [Agents - Actions]: https://docs.unity3d.com/Packages/com.unity.ml-agents@latest/index.html?subfolder=/manual/Learning-Environment-Design-Agents.html#actions + /// + /// + /// + /// Struct containing the buffers of actions to be executed at this step. + /// + public virtual void OnActionReceived(ActionBuffers actions) { } + + /// + /// Implement `OnEpisodeBegin()` to set up an Agent instance at the beginning + /// of an episode. + /// + /// + /// + public virtual void OnEpisodeBegin() { } + + /// + /// Gets the most recent ActionBuffer for this agent. + /// + /// The most recent ActionBuffer for this agent + public ActionBuffers GetStoredActionBuffers() + { + return m_ActuatorManager.StoredActions; + } + + /// + /// An internal reset method that updates internal data structures in + /// addition to calling . + /// + void _AgentReset() + { + ResetData(); + m_StepCount = 0; + using (m_OnEpisodeBeginChecker.Start()) + { + OnEpisodeBegin(); + } + } + + /// + /// Scales continuous action from [-1, 1] to arbitrary range. + /// + /// The input action value. + /// The minimum output value. + /// The maximum output value. + /// The scaled from [-1,1] to + /// [, ]. + protected static float ScaleAction(float rawAction, float min, float max) + { + var middle = (min + max) / 2; + var range = (max - min) / 2; + return rawAction * range + middle; + } + + /// + /// Signals the agent that it must send its decision to the brain. + /// + void SendInfo() + { + // If the Agent is done, it has just reset and thus requires a new decision + if (m_RequestDecision) + { + SendInfoToBrain(); + m_Reward = 0f; + m_GroupReward = 0f; + m_RequestDecision = false; + } + } + + void AgentIncrementStep() + { + m_StepCount += 1; + } + + /// Used by the brain to make the agent perform a step. + void AgentStep() + { + if ((m_RequestAction) && (m_Brain != null)) + { + m_RequestAction = false; + m_ActuatorManager.ExecuteActions(); + } + + if ((m_StepCount >= MaxStep) && (MaxStep > 0)) + { + NotifyAgentDone(DoneReason.MaxStepReached); + _AgentReset(); + } + } + + void DecideAction() + { + if (m_ActuatorManager.StoredActions.ContinuousActions.Array == null) + { + ResetData(); + } + var actions = m_Brain?.DecideAction() ?? new ActionBuffers(); + m_Info.CopyActions(actions); + m_ActuatorManager.UpdateActions(actions); + } + + internal void SetMultiAgentGroup(IMultiAgentGroup multiAgentGroup) + { + if (multiAgentGroup == null) + { + m_GroupId = 0; + } + else + { + var newGroupId = multiAgentGroup.GetId(); + if (m_GroupId == 0 || m_GroupId == newGroupId) + { + m_GroupId = newGroupId; + } + else + { + throw new UnityAgentsException("Agent is already registered with a group. Unregister it first."); + } + } + } + } +} diff --git a/com.unity.ml-agents/Runtime/Agent.cs.meta b/com.unity.ml-agents/Runtime/Agent.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..5463d244fb33540d5f7dfa8b9fada005a763c721 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Agent.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Analytics.meta b/com.unity.ml-agents/Runtime/Analytics.meta new file mode 100644 index 0000000000000000000000000000000000000000..260b85a9b3a13afb47fb6a4a786a6d82663e37e1 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Analytics.meta differ diff --git a/com.unity.ml-agents/Runtime/Analytics/AnalyticsUtils.cs b/com.unity.ml-agents/Runtime/Analytics/AnalyticsUtils.cs new file mode 100644 index 0000000000000000000000000000000000000000..1d906a0aca0f064cd5b9e52e572e3e93679327cd --- /dev/null +++ b/com.unity.ml-agents/Runtime/Analytics/AnalyticsUtils.cs @@ -0,0 +1,73 @@ +using System; +using System.Text; +using System.Security.Cryptography; +using UnityEngine; + +namespace Unity.MLAgents.Analytics +{ + internal static class AnalyticsUtils + { + /// + /// Conversion function from byte array to hex string + /// + /// + /// A byte array to be hex encoded. + private static string ToHexString(byte[] array) + { + StringBuilder hex = new StringBuilder(array.Length * 2); + foreach (byte b in array) + { + hex.AppendFormat("{0:x2}", b); + } + return hex.ToString(); + } + + /// + /// Hash a string to remove PII or secret info before sending to analytics + /// + /// + /// A string containing the key to be used for HMAC encoding. + /// + /// A string containing the value to be encoded. + public static string Hash(string key, string value) + { + string hash; + UTF8Encoding encoder = new UTF8Encoding(); + using (HMACSHA256 hmac = new HMACSHA256(encoder.GetBytes(key))) + { + Byte[] hmBytes = hmac.ComputeHash(encoder.GetBytes(value)); + hash = ToHexString(hmBytes); + } + return hash; + } + + internal static bool s_SendEditorAnalytics = true; + +#if UNITY_EDITOR + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] + static void ResetStaticsOnLoad() + { + s_SendEditorAnalytics = true; + } +#endif + + /// + /// Helper class to temporarily disable sending analytics from unit tests. + /// + internal class DisableAnalyticsSending : IDisposable + { + private bool m_PreviousSendEditorAnalytics; + + public DisableAnalyticsSending() + { + m_PreviousSendEditorAnalytics = s_SendEditorAnalytics; + s_SendEditorAnalytics = false; + } + + public void Dispose() + { + s_SendEditorAnalytics = m_PreviousSendEditorAnalytics; + } + } + } +} diff --git a/com.unity.ml-agents/Runtime/Analytics/AnalyticsUtils.cs.meta b/com.unity.ml-agents/Runtime/Analytics/AnalyticsUtils.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..b00fab1c903ddd31d5c901b779be1b043107cd4b Binary files /dev/null and b/com.unity.ml-agents/Runtime/Analytics/AnalyticsUtils.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Analytics/Events.cs b/com.unity.ml-agents/Runtime/Analytics/Events.cs new file mode 100644 index 0000000000000000000000000000000000000000..d1a58de1dbfa0faed827487aba6d0e7446d05eb4 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Analytics/Events.cs @@ -0,0 +1,244 @@ +using System; +using System.Collections.Generic; +using Unity.MLAgents.Actuators; +using Unity.MLAgents.Sensors; +using UnityEngine.Analytics; + +namespace Unity.MLAgents.Analytics +{ + internal static class AnalyticsConstants + { + public const string k_VendorKey = "unity.ml-agents"; + + /// + /// Maximum number of events sent per hour. + /// + public const int k_MaxEventsPerHour = 1000; + + /// + /// Maximum number of items in an event. + /// + public const int k_MaxNumberOfElements = 1000; + } + + [Serializable] + [AnalyticInfo(eventName: "ml_agents_inferencemodelset", version: 1, vendorKey: AnalyticsConstants.k_VendorKey, maxEventsPerHour: AnalyticsConstants.k_MaxEventsPerHour, maxNumberOfElements: AnalyticsConstants.k_MaxNumberOfElements)] + internal class InferenceEvent : IAnalytic.IData, IAnalytic + { + /// + /// Hash of the BehaviorName. + /// + public string BehaviorName; + public string SentisModelSource; + public long SentisModelVersion; + public string SentisModelProducer; + public string SentisPackageVersion; + /// + /// Whether inference is performed on CPU (0) or GPU (1). + /// + public int InferenceDevice; + public List ObservationSpecs; + public EventActionSpec ActionSpec; + public List ActuatorInfos; + public int MemorySize; + public long TotalWeightSizeBytes; + public string ModelHash; + public bool TryGatherData(out IAnalytic.IData data, out Exception error) + { + data = this; + error = null; + return true; + } + } + + /// + /// Simplified version of ActionSpec struct for use in analytics + /// + [Serializable] + internal struct EventActionSpec + { + public int NumContinuousActions; + public int NumDiscreteActions; + public int[] BranchSizes; + + public static EventActionSpec FromActionSpec(ActionSpec actionSpec) + { + var branchSizes = actionSpec.BranchSizes ?? Array.Empty(); + return new EventActionSpec + { + NumContinuousActions = actionSpec.NumContinuousActions, + NumDiscreteActions = actionSpec.NumDiscreteActions, + BranchSizes = branchSizes, + }; + } + } + + /// + /// Information about an actuator. + /// + [Serializable] + internal struct EventActuatorInfo + { + public int BuiltInActuatorType; + public int NumContinuousActions; + public int NumDiscreteActions; + + public static EventActuatorInfo FromActuator(IActuator actuator) + { + BuiltInActuatorType builtInActuatorType = Actuators.BuiltInActuatorType.Unknown; + if (actuator is IBuiltInActuator builtInActuator) + { + builtInActuatorType = builtInActuator.GetBuiltInActuatorType(); + } + + var actionSpec = actuator.ActionSpec; + + return new EventActuatorInfo + { + BuiltInActuatorType = (int)builtInActuatorType, + NumContinuousActions = actionSpec.NumContinuousActions, + NumDiscreteActions = actionSpec.NumDiscreteActions + }; + } + } + + /// + /// Information about one dimension of an observation. + /// + [Serializable] + internal struct EventObservationDimensionInfo + { + public int Size; + public int Flags; + } + + /// + /// Simplified summary of Agent observations for use in analytics + /// + [Serializable] + internal struct EventObservationSpec + { + public string SensorName; + public string CompressionType; + public int BuiltInSensorType; + public int ObservationType; + public EventObservationDimensionInfo[] DimensionInfos; + + public static EventObservationSpec FromSensor(ISensor sensor) + { + var obsSpec = sensor.GetObservationSpec(); + var shape = obsSpec.Shape; + var dimProps = obsSpec.DimensionProperties; + var dimInfos = new EventObservationDimensionInfo[shape.Length]; + for (var i = 0; i < shape.Length; i++) + { + dimInfos[i].Size = shape[i]; + dimInfos[i].Flags = (int)dimProps[i]; + } + + var builtInSensorType = + (sensor as IBuiltInSensor)?.GetBuiltInSensorType() ?? Sensors.BuiltInSensorType.Unknown; + + return new EventObservationSpec + { + SensorName = sensor.GetName(), + CompressionType = sensor.GetCompressionSpec().SensorCompressionType.ToString(), + BuiltInSensorType = (int)builtInSensorType, + ObservationType = (int)obsSpec.ObservationType, + DimensionInfos = dimInfos, + }; + } + } + + [Serializable] + [AnalyticInfo(eventName: "ml_agents_remote_policy_initialized", vendorKey: AnalyticsConstants.k_VendorKey, maxEventsPerHour: AnalyticsConstants.k_MaxEventsPerHour, maxNumberOfElements: AnalyticsConstants.k_MaxNumberOfElements)] + internal class RemotePolicyInitializedEvent : IAnalytic.IData, IAnalytic + { + public string TrainingSessionGuid; + /// + /// Hash of the BehaviorName. + /// + public string BehaviorName; + public List ObservationSpecs; + public EventActionSpec ActionSpec; + public List ActuatorInfos; + + /// + /// This will be the same as TrainingEnvironmentInitializedEvent if available, but + /// TrainingEnvironmentInitializedEvent maybe not always be available with older trainers. + /// + public string MLAgentsEnvsVersion; + public string TrainerCommunicationVersion; + public bool TryGatherData(out IAnalytic.IData data, out Exception error) + { + data = this; + error = null; + return true; + } + } + + + [Serializable] + [AnalyticInfo(eventName: "ml_agents_training_environment_initialized", vendorKey: AnalyticsConstants.k_VendorKey, maxEventsPerHour: AnalyticsConstants.k_MaxEventsPerHour, maxNumberOfElements: AnalyticsConstants.k_MaxNumberOfElements)] + internal class TrainingEnvironmentInitializedEvent : IAnalytic.IData, IAnalytic + { + public string TrainingSessionGuid; + + public string TrainerPythonVersion; + public string MLAgentsVersion; + public string MLAgentsEnvsVersion; + public string TorchVersion; + public string TorchDeviceType; + public int NumEnvironments; + public int NumEnvironmentParameters; + public string RunOptions; + public bool TryGatherData(out IAnalytic.IData data, out Exception error) + { + data = this; + error = null; + return true; + } + } + + [Flags] + internal enum RewardSignals + { + Extrinsic = 1 << 0, + Gail = 1 << 1, + Curiosity = 1 << 2, + Rnd = 1 << 3, + } + + [Flags] + internal enum TrainingFeatures + { + BehavioralCloning = 1 << 0, + Recurrent = 1 << 1, + Threaded = 1 << 2, + SelfPlay = 1 << 3, + Curriculum = 1 << 4, + } + + [Serializable] + [AnalyticInfo(eventName: "ml_agents_training_behavior_initialized", vendorKey: AnalyticsConstants.k_VendorKey, maxEventsPerHour: AnalyticsConstants.k_MaxEventsPerHour, maxNumberOfElements: AnalyticsConstants.k_MaxNumberOfElements)] + internal class TrainingBehaviorInitializedEvent : IAnalytic.IData, IAnalytic + { + public string TrainingSessionGuid; + + public string BehaviorName; + public string TrainerType; + public RewardSignals RewardSignalFlags; + public TrainingFeatures TrainingFeatureFlags; + public string VisualEncoder; + public int NumNetworkLayers; + public int NumNetworkHiddenUnits; + public string Config; + + public bool TryGatherData(out IAnalytic.IData data, out Exception error) + { + data = this; + error = null; + return true; + } + } +} diff --git a/com.unity.ml-agents/Runtime/Analytics/Events.cs.meta b/com.unity.ml-agents/Runtime/Analytics/Events.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..347eebcd518c4c2b65417d1c08cb73f11eb5217b Binary files /dev/null and b/com.unity.ml-agents/Runtime/Analytics/Events.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Analytics/InferenceAnalytics.cs b/com.unity.ml-agents/Runtime/Analytics/InferenceAnalytics.cs new file mode 100644 index 0000000000000000000000000000000000000000..899d71f8b2686f8857fae920a2aaceb752dfdc00 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Analytics/InferenceAnalytics.cs @@ -0,0 +1,249 @@ +using System.Collections.Generic; +using System.Diagnostics; +using Unity.InferenceEngine; +using Unity.MLAgents.Actuators; +using Unity.MLAgents.Inference; +using Unity.MLAgents.Policies; +using Unity.MLAgents.Sensors; +using UnityEngine; + +#if MLA_UNITY_ANALYTICS_MODULE && ENABLE_CLOUD_SERVICES_ANALYTICS +using UnityEngine.Analytics; +#endif + + +#if UNITY_EDITOR +using UnityEditor; +#if MLA_UNITY_ANALYTICS_MODULE +using UnityEditor.Analytics; +#endif // MLA_UNITY_ANALYTICS_MODULE +#endif // UNITY_EDITOR + + +namespace Unity.MLAgents.Analytics +{ + internal class InferenceAnalytics + { + + +#if UNITY_EDITOR && MLA_UNITY_ANALYTICS_MODULE && ENABLE_CLOUD_SERVICES_ANALYTICS + /// + /// Models that we've already sent events for. + /// + private static HashSet s_SentModels; +#endif + + static bool EnableAnalytics() + { +#if UNITY_EDITOR && MLA_UNITY_ANALYTICS_MODULE && ENABLE_CLOUD_SERVICES_ANALYTICS + + if (s_SentModels == null) + { + s_SentModels = new HashSet(); + } + + return true; + +#else // no editor, no analytics + return false; +#endif + } + + public static bool IsAnalyticsEnabled() + { +#if UNITY_EDITOR + return EditorAnalytics.enabled; +#else + return false; +#endif + } + + /// + /// Send an analytics event for the NNModel when it is set up for inference. + /// No events will be sent if analytics are disabled, and at most one event + /// will be sent per model instance. + /// + /// The NNModel being used for inference. + /// The BehaviorName of the Agent using the model + /// Whether inference is being performed on the CPU or GPU + /// List of ISensors for the Agent. Used to generate information about the observation space. + /// ActionSpec for the Agent. Used to generate information about the action space. + /// List of IActuators for the Agent. Used to generate information about the action space. + [Conditional("MLA_UNITY_ANALYTICS_MODULE")] + public static void InferenceModelSet( + ModelAsset nnModel, + string behaviorName, + InferenceDevice inferenceDevice, + IList sensors, + ActionSpec actionSpec, + IList actuators + ) + { +#if UNITY_EDITOR && MLA_UNITY_ANALYTICS_MODULE && ENABLE_CLOUD_SERVICES_ANALYTICS + // The event shouldn't be able to report if this is disabled but if we know we're not going to report + // Lets early out and not waste time gathering all the data + if (!IsAnalyticsEnabled()) + return; + + if (!EnableAnalytics()) + return; + + var added = s_SentModels.Add(nnModel); + + if (!added) + { + // We previously added this model. Exit so we don't resend. + return; + } + + var data = GetEventForModel(nnModel, behaviorName, inferenceDevice, sensors, actionSpec, actuators); + // Note - to debug, use JsonUtility.ToJson on the event. + // Debug.Log(JsonUtility.ToJson(data, true)); + if (AnalyticsUtils.s_SendEditorAnalytics) + { + EditorAnalytics.SendAnalytic(data); + } +#endif + } + + /// + /// Generate an InferenceEvent for the model. + /// + /// + /// + /// + /// + /// + /// + /// `InferenceEvent` from the input model. + internal static InferenceEvent GetEventForModel( + ModelAsset nnModel, + string behaviorName, + InferenceDevice inferenceDevice, + IList sensors, + ActionSpec actionSpec, + IList actuators + ) + { + var sentisModel = ModelLoader.Load(nnModel); + using var sentisModelInfo = new SentisModelInfo(sentisModel); + var inferenceEvent = new InferenceEvent(); + + // Hash the behavior name so that there's no concern about PII or "secret" data being leaked. + inferenceEvent.BehaviorName = AnalyticsUtils.Hash(AnalyticsConstants.k_VendorKey, behaviorName); + + inferenceEvent.SentisModelVersion = sentisModelInfo.Version; + inferenceEvent.SentisModelProducer = sentisModel.ProducerName; + inferenceEvent.MemorySize = sentisModelInfo.MemorySize; + inferenceEvent.InferenceDevice = (int)inferenceDevice; + + // TODO deprecate tensorflow conversion + if (sentisModel.ProducerName == "Script") + { + // .nn files don't have these fields set correctly. Assign some placeholder values. + inferenceEvent.SentisModelSource = "NN"; + inferenceEvent.SentisModelProducer = "tensorflow_to_barracuda.py"; + } + +#if UNITY_EDITOR + var sentisPackageInfo = UnityEditor.PackageManager.PackageInfo.FindForAssembly(typeof(Tensor).Assembly); + inferenceEvent.SentisPackageVersion = sentisPackageInfo.version; +#else + inferenceEvent.SentisPackageVersion = null; +#endif + + inferenceEvent.ActionSpec = EventActionSpec.FromActionSpec(actionSpec); + inferenceEvent.ObservationSpecs = new List(sensors.Count); + foreach (var sensor in sensors) + { + inferenceEvent.ObservationSpecs.Add(EventObservationSpec.FromSensor(sensor)); + } + + inferenceEvent.ActuatorInfos = new List(actuators.Count); + foreach (var actuator in actuators) + { + inferenceEvent.ActuatorInfos.Add(EventActuatorInfo.FromActuator(actuator)); + } + + inferenceEvent.TotalWeightSizeBytes = GetModelWeightSize(sentisModel); + inferenceEvent.ModelHash = GetModelHash(sentisModel); + return inferenceEvent; + } + + /// + /// Compute the total model weight size in bytes. + /// This corresponds to the "Total weight size" display in the Sentis inspector, + /// and the calculations are the same. + /// + /// + /// The total model weight size in bytes. + static long GetModelWeightSize(Model sentisModel) + { + long totalWeightsSizeInBytes = 0; + for (var c = 0; c < sentisModel.constants.Count; c++) + { + totalWeightsSizeInBytes += sentisModel.constants[c].lengthBytes; + } + return totalWeightsSizeInBytes; + } + + /// + /// Wrapper around Hash128 that supports Append(float[], int, int) + /// + struct MLAgentsHash128 + { + private Hash128 m_Hash; + + public void Append(float[] values, int count) + { + if (values == null) + { + return; + } + + // Pre-2020 versions of Unity don't have Hash128.Append() (can only hash strings and scalars) + // For these versions, we'll hash element by element. +#if UNITY_2020_1_OR_NEWER + m_Hash.Append(values, 0, count); +#else + for (var i = 0; i < count; i++) + { + var tempHash = new Hash128(); + HashUtilities.ComputeHash128(ref values[i], ref tempHash); + HashUtilities.AppendHash(ref tempHash, ref m_Hash); + } +#endif + } + + public void Append(string value) + { + var tempHash = Hash128.Compute(value); + HashUtilities.AppendHash(ref tempHash, ref m_Hash); + } + + public override string ToString() + { + return m_Hash.ToString(); + } + } + + /// + /// Compute a hash of the model's layer data and return it as a string. + /// A subset of the layer weights are used for performance. + /// This increases the chance of a collision, but this should still be extremely rare. + /// + /// + /// The hash of the model's layer data. + static string GetModelHash(Model sentisModel) + { + var hash = new MLAgentsHash128(); + + foreach (var constant in sentisModel.constants) + { + hash.Append(constant.ToString()); + } + + return hash.ToString(); + } + } +} diff --git a/com.unity.ml-agents/Runtime/Analytics/InferenceAnalytics.cs.meta b/com.unity.ml-agents/Runtime/Analytics/InferenceAnalytics.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..e81b2ecbb6da4cbaeb14da154b502921669e704c Binary files /dev/null and b/com.unity.ml-agents/Runtime/Analytics/InferenceAnalytics.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Analytics/TrainingAnalytics.cs b/com.unity.ml-agents/Runtime/Analytics/TrainingAnalytics.cs new file mode 100644 index 0000000000000000000000000000000000000000..62e9aedfe28c5a633a2b1eac2a2a0d7a1694291f --- /dev/null +++ b/com.unity.ml-agents/Runtime/Analytics/TrainingAnalytics.cs @@ -0,0 +1,251 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using Unity.MLAgents.Actuators; +using Unity.MLAgents.Sensors; +using UnityEngine; +#if MLA_UNITY_ANALYTICS_MODULE + +#if ENABLE_CLOUD_SERVICES_ANALYTICS +using UnityEngine.Analytics; +#endif + +#if UNITY_EDITOR +using UnityEditor.Analytics; +#endif +#endif + +#if UNITY_EDITOR +using UnityEditor; +#endif + +namespace Unity.MLAgents.Analytics +{ + internal static class TrainingAnalytics + { + private static bool s_SentEnvironmentInitialized; + +#if UNITY_EDITOR && MLA_UNITY_ANALYTICS_MODULE && ENABLE_CLOUD_SERVICES_ANALYTICS + + /// + /// Behaviors that we've already sent events for. + /// + private static HashSet s_SentRemotePolicyInitialized; + private static HashSet s_SentTrainingBehaviorInitialized; +#endif + + private static Guid? s_TrainingSessionGuid; + + // These are set when the RpcCommunicator connects + private static string s_TrainerPackageVersion = ""; + private static string s_TrainerCommunicationVersion = ""; + +#if UNITY_EDITOR + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] + static void ResetStaticsOnLoad() + { +#if MLA_UNITY_ANALYTICS_MODULE && ENABLE_CLOUD_SERVICES_ANALYTICS + s_SentRemotePolicyInitialized = null; + s_SentTrainingBehaviorInitialized = null; +#endif + s_TrainingSessionGuid = null; + s_TrainerPackageVersion = ""; + s_TrainerCommunicationVersion = ""; + } +#endif + + internal static bool EnableAnalytics() + { +#if UNITY_EDITOR && MLA_UNITY_ANALYTICS_MODULE && ENABLE_CLOUD_SERVICES_ANALYTICS + + if (s_SentRemotePolicyInitialized == null) + { + s_SentRemotePolicyInitialized = new HashSet(); + s_SentTrainingBehaviorInitialized = new HashSet(); + s_TrainingSessionGuid = Guid.NewGuid(); + } + + return true; +#else + return false; +#endif // MLA_UNITY_ANALYTICS_MODULE + } + + /// + /// Cache information about the trainer when it becomes available in the RpcCommunicator. + /// + /// + /// + [Conditional("MLA_UNITY_ANALYTICS_MODULE")] + public static void SetTrainerInformation(string packageVersion, string communicationVersion) + { + s_TrainerPackageVersion = packageVersion; + s_TrainerCommunicationVersion = communicationVersion; + } + + public static bool IsAnalyticsEnabled() + { +#if UNITY_EDITOR && MLA_UNITY_ANALYTICS_MODULE && ENABLE_CLOUD_SERVICES_ANALYTICS + return EditorAnalytics.enabled; +#else + return false; +#endif + } + + [Conditional("MLA_UNITY_ANALYTICS_MODULE")] + public static void TrainingEnvironmentInitialized(TrainingEnvironmentInitializedEvent tbiEvent) + { + if (!IsAnalyticsEnabled()) + return; + + if (!EnableAnalytics()) + return; + + if (s_SentEnvironmentInitialized) + { + // We already sent an TrainingEnvironmentInitializedEvent. Exit so we don't resend. + return; + } + + s_SentEnvironmentInitialized = true; + tbiEvent.TrainingSessionGuid = s_TrainingSessionGuid.ToString(); + + // Note - to debug, use JsonUtility.ToJson on the event. + // Debug.Log( + // $"Would send event ml_agents_training_environment_initialized with body {JsonUtility.ToJson(tbiEvent, true)}" + // ); +#if UNITY_EDITOR && MLA_UNITY_ANALYTICS_MODULE && ENABLE_CLOUD_SERVICES_ANALYTICS + if (AnalyticsUtils.s_SendEditorAnalytics) + { + EditorAnalytics.SendAnalytic(tbiEvent); + } +#endif + } + + [Conditional("MLA_UNITY_ANALYTICS_MODULE")] + public static void RemotePolicyInitialized( + string fullyQualifiedBehaviorName, + IList sensors, + ActionSpec actionSpec, + IList actuators + ) + { +#if UNITY_EDITOR && MLA_UNITY_ANALYTICS_MODULE && ENABLE_CLOUD_SERVICES_ANALYTICS + if (!IsAnalyticsEnabled()) + return; + + if (!EnableAnalytics()) + return; + + // Extract base behavior name (no team ID) + var behaviorName = ParseBehaviorName(fullyQualifiedBehaviorName); + var added = s_SentRemotePolicyInitialized.Add(behaviorName); + + if (!added) + { + // We previously added this model. Exit so we don't resend. + return; + } + + var data = GetEventForRemotePolicy(behaviorName, sensors, actionSpec, actuators); + // Note - to debug, use JsonUtility.ToJson on the event. + // Debug.Log( + // $"Would send event ml_agents_remote_policy_initialized with body {JsonUtility.ToJson(data, true)}" + // ); + if (AnalyticsUtils.s_SendEditorAnalytics) + { + EditorAnalytics.SendAnalytic(data); + } +#endif + } + + internal static string ParseBehaviorName(string fullyQualifiedBehaviorName) + { + var lastQuestionIndex = fullyQualifiedBehaviorName.LastIndexOf("?"); + if (lastQuestionIndex < 0) + { + // Nothing to remove + return fullyQualifiedBehaviorName; + } + + return fullyQualifiedBehaviorName.Substring(0, lastQuestionIndex); + } + + internal static TrainingBehaviorInitializedEvent SanitizeTrainingBehaviorInitializedEvent(TrainingBehaviorInitializedEvent tbiEvent) + { + // Hash the behavior name if the message version is from an older version of ml-agents that doesn't do trainer-side hashing. + // We'll also, for extra safety, verify that the BehaviorName is the size of the expected SHA256 hash. + // Context: The config field was added at the same time as trainer side hashing, so messages including it should already be hashed. + if (tbiEvent.Config.Length == 0 || tbiEvent.BehaviorName.Length != 64) + { + tbiEvent.BehaviorName = AnalyticsUtils.Hash(AnalyticsConstants.k_VendorKey, tbiEvent.BehaviorName); + } + + return tbiEvent; + } + + [Conditional("MLA_UNITY_ANALYTICS_MODULE")] + public static void TrainingBehaviorInitialized(TrainingBehaviorInitializedEvent rawTbiEvent) + { +#if UNITY_EDITOR && MLA_UNITY_ANALYTICS_MODULE && ENABLE_CLOUD_SERVICES_ANALYTICS + if (!IsAnalyticsEnabled()) + return; + + if (!EnableAnalytics()) + return; + + var tbiEvent = SanitizeTrainingBehaviorInitializedEvent(rawTbiEvent); + var behaviorName = tbiEvent.BehaviorName; + var added = s_SentTrainingBehaviorInitialized.Add(behaviorName); + + if (!added) + { + // We previously added this model. Exit so we don't resend. + return; + } + + tbiEvent.TrainingSessionGuid = s_TrainingSessionGuid.ToString(); + + // Note - to debug, use JsonUtility.ToJson on the event. + // Debug.Log( + // $"Would send event ml_agents_training_behavior_initialized with body {JsonUtility.ToJson(tbiEvent, true)}" + // ); + if (AnalyticsUtils.s_SendEditorAnalytics) + { + EditorAnalytics.SendAnalytic(tbiEvent); + } +#endif + } + + internal static RemotePolicyInitializedEvent GetEventForRemotePolicy( + string behaviorName, + IList sensors, + ActionSpec actionSpec, + IList actuators + ) + { + var remotePolicyEvent = new RemotePolicyInitializedEvent(); + + // Hash the behavior name so that there's no concern about PII or "secret" data being leaked. + remotePolicyEvent.BehaviorName = AnalyticsUtils.Hash(AnalyticsConstants.k_VendorKey, behaviorName); + + remotePolicyEvent.TrainingSessionGuid = s_TrainingSessionGuid.ToString(); + remotePolicyEvent.ActionSpec = EventActionSpec.FromActionSpec(actionSpec); + remotePolicyEvent.ObservationSpecs = new List(sensors.Count); + foreach (var sensor in sensors) + { + remotePolicyEvent.ObservationSpecs.Add(EventObservationSpec.FromSensor(sensor)); + } + + remotePolicyEvent.ActuatorInfos = new List(actuators.Count); + foreach (var actuator in actuators) + { + remotePolicyEvent.ActuatorInfos.Add(EventActuatorInfo.FromActuator(actuator)); + } + + remotePolicyEvent.MLAgentsEnvsVersion = s_TrainerPackageVersion; + remotePolicyEvent.TrainerCommunicationVersion = s_TrainerCommunicationVersion; + return remotePolicyEvent; + } + } +} diff --git a/com.unity.ml-agents/Runtime/Analytics/TrainingAnalytics.cs.meta b/com.unity.ml-agents/Runtime/Analytics/TrainingAnalytics.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..9109c265a2fc55fcfe025bbc9e44578a37301533 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Analytics/TrainingAnalytics.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Areas.meta b/com.unity.ml-agents/Runtime/Areas.meta new file mode 100644 index 0000000000000000000000000000000000000000..d00b0cf67c53203f2e475fa456af912f30233f0f Binary files /dev/null and b/com.unity.ml-agents/Runtime/Areas.meta differ diff --git a/com.unity.ml-agents/Runtime/Areas/TrainingAreaReplicator.cs b/com.unity.ml-agents/Runtime/Areas/TrainingAreaReplicator.cs new file mode 100644 index 0000000000000000000000000000000000000000..c47383b4a3052cbc9d2ab7aadcb9e1413c0fd8c2 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Areas/TrainingAreaReplicator.cs @@ -0,0 +1,126 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace Unity.MLAgents.Areas +{ + /// + /// The Training Ares Replicator allows for a training area object group to be replicated dynamically during runtime. + /// + [DefaultExecutionOrder(-5)] + public class TrainingAreaReplicator : MonoBehaviour + { + /// + /// The base training area to be replicated. + /// + public GameObject baseArea; + + /// + /// The number of training areas to replicate. + /// + public int numAreas = 1; + + /// + /// The separation between each training area. + /// + public float separation = 10f; + + /// + /// Whether to replicate in the editor or in a build only. Default = true + /// + public bool buildOnly = true; + + int3 m_GridSize = new(1, 1, 1); + int m_AreaCount; + string m_TrainingAreaName; + + /// + /// The size of the computed grid to pack the training areas into. + /// + public int3 GridSize => m_GridSize; + + /// + /// The name of the training area. + /// + public string TrainingAreaName => m_TrainingAreaName; + + /// + /// Called before the simulation begins to computed the grid size for distributing + /// the replicated training areas and set the area name. + /// + public void Awake() + { + // Computes the Grid Size on Awake + ComputeGridSize(); + // Sets the TrainingArea name to the name of the base area. + m_TrainingAreaName = baseArea.name; + } + + /// + /// Called after Awake and before the simulation begins and adds the training areas before + /// the Academy begins. + /// + public void OnEnable() + { + // Adds the training as replicas during OnEnable to ensure they are added before the Academy begins its work. + if (buildOnly) + { +#if UNITY_STANDALONE && !UNITY_EDITOR + AddEnvironments(); +#endif + return; + } + AddEnvironments(); + } + + /// + /// Computes the Grid Size for replicating the training area. + /// + void ComputeGridSize() + { + // check if running inference, if so, use the num areas set through the component, + // otherwise, pull it from the academy + if (Academy.Instance.Communicator != null) + numAreas = Academy.Instance.NumAreas; + + var rootNumAreas = Mathf.Pow(numAreas, 1.0f / 3.0f); + m_GridSize.x = Mathf.CeilToInt(rootNumAreas); + m_GridSize.y = Mathf.CeilToInt(rootNumAreas); + var zSize = Mathf.CeilToInt((float)numAreas / (m_GridSize.x * m_GridSize.y)); + m_GridSize.z = zSize == 0 ? 1 : zSize; + } + + /// + /// Adds replicas of the training area to the scene. + /// + /// + void AddEnvironments() + { + if (numAreas > m_GridSize.x * m_GridSize.y * m_GridSize.z) + { + throw new UnityAgentsException("The number of training areas that you have specified exceeds the size of the grid."); + } + + for (int z = 0; z < m_GridSize.z; z++) + { + for (int y = 0; y < m_GridSize.y; y++) + { + for (int x = 0; x < m_GridSize.x; x++) + { + if (m_AreaCount == 0) + { + // Skip this first area since it already exists. + m_AreaCount = 1; + } + else if (m_AreaCount < numAreas) + { + m_AreaCount++; + var area = Instantiate(baseArea, new Vector3(x * separation, y * separation, z * separation), Quaternion.identity); + area.name = m_TrainingAreaName; + } + } + } + } + } + } +} diff --git a/com.unity.ml-agents/Runtime/Areas/TrainingAreaReplicator.cs.meta b/com.unity.ml-agents/Runtime/Areas/TrainingAreaReplicator.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..84ac36d7899a9a2ed915c1cffe163525142d629e Binary files /dev/null and b/com.unity.ml-agents/Runtime/Areas/TrainingAreaReplicator.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/AssemblyInfo.cs b/com.unity.ml-agents/Runtime/AssemblyInfo.cs new file mode 100644 index 0000000000000000000000000000000000000000..0f19e37d7f3232454b425c8cc5e7c03b59432915 --- /dev/null +++ b/com.unity.ml-agents/Runtime/AssemblyInfo.cs @@ -0,0 +1,13 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Unity.ML-Agents.Editor.Tests")] +[assembly: InternalsVisibleTo("Unity.ML-Agents.Tests")] +[assembly: InternalsVisibleTo("Unity.ML-Agents.Runtime.Sensor.Tests")] +[assembly: InternalsVisibleTo("Unity.ML-Agents.Runtime.Utils.Tests")] +[assembly: InternalsVisibleTo("Unity.ML-Agents.Runtime.Tests")] +[assembly: InternalsVisibleTo("Unity.ML-Agents.Runtime.Input.Tests")] +[assembly: InternalsVisibleTo("Unity.ML-Agents.Input")] +[assembly: InternalsVisibleTo("Unity.ML-Agents.Editor")] +[assembly: InternalsVisibleTo("Unity.ML-Agents.Pro")] +[assembly: InternalsVisibleTo("Unity.ML-Agents.Pro.Tests")] +[assembly: InternalsVisibleTo("MLAgentsExamples.Tests.Performance")] diff --git a/com.unity.ml-agents/Runtime/AssemblyInfo.cs.meta b/com.unity.ml-agents/Runtime/AssemblyInfo.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..1672ad458e41fd3d5819426205e294f6b74660b2 Binary files /dev/null and b/com.unity.ml-agents/Runtime/AssemblyInfo.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Communicator.meta b/com.unity.ml-agents/Runtime/Communicator.meta new file mode 100644 index 0000000000000000000000000000000000000000..dc3a8bac9bc79d2cbe5e01e0ff68b84939ecbf88 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Communicator.meta differ diff --git a/com.unity.ml-agents/Runtime/Communicator/CommunicatorFactory.cs b/com.unity.ml-agents/Runtime/Communicator/CommunicatorFactory.cs new file mode 100644 index 0000000000000000000000000000000000000000..e3998701725b9238c558e73b42d5238d2dce9b7f --- /dev/null +++ b/com.unity.ml-agents/Runtime/Communicator/CommunicatorFactory.cs @@ -0,0 +1,63 @@ +using System; +using UnityEngine; + +namespace Unity.MLAgents +{ + /// + /// Factory class for an ICommunicator instance. This is used to the at startup. + /// By default, on desktop platforms, an ICommunicator will be created and attempt to connect + /// to a trainer. This behavior can be prevented by setting to false + /// *before* the is initialized. + /// + public static class CommunicatorFactory + { + static Func s_Creator; + static bool s_Enabled = true; + + /// + /// Whether or not an ICommunicator instance will be created when the is initialized. + /// Changing this has no effect after the has already been initialized. + /// + public static bool Enabled + { + get => s_Enabled; + set => s_Enabled = value; + } + +#if UNITY_EDITOR + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] + static void ResetStaticsOnLoad() + { + s_Creator = null; + s_Enabled = true; + } +#endif + /// + /// Check if a communicator has been registered. + /// + public static bool CommunicatorRegistered => s_Creator != null; + + internal static ICommunicator Create() + { + return s_Enabled ? s_Creator() : null; + } + + /// + /// Register a function that will create an ICommunicator instance. + /// + /// Creator + /// Type of communicator + public static void Register(Func creator) where T : ICommunicator + { + s_Creator = () => creator(); + } + + /// + /// Clear the registered creator. + /// + public static void ClearCreator() + { + s_Creator = null; + } + } +} diff --git a/com.unity.ml-agents/Runtime/Communicator/CommunicatorFactory.cs.meta b/com.unity.ml-agents/Runtime/Communicator/CommunicatorFactory.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..1d208003e3ee4e139b0c52a63d3bc05e65376e20 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Communicator/CommunicatorFactory.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Communicator/GrpcExtensions.cs b/com.unity.ml-agents/Runtime/Communicator/GrpcExtensions.cs new file mode 100644 index 0000000000000000000000000000000000000000..4ccee1a1642d4716b03a36525df0fd85f034c54a --- /dev/null +++ b/com.unity.ml-agents/Runtime/Communicator/GrpcExtensions.cs @@ -0,0 +1,550 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Google.Protobuf; +using Unity.MLAgents.CommunicatorObjects; +using UnityEngine; +using System.Runtime.CompilerServices; +using Unity.MLAgents.Actuators; +using Unity.MLAgents.Sensors; +using Unity.MLAgents.Demonstrations; +using Unity.MLAgents.Policies; + +using Unity.MLAgents.Analytics; + +[assembly: InternalsVisibleTo("Unity.ML-Agents.Editor")] +[assembly: InternalsVisibleTo("Unity.ML-Agents.Editor.Tests")] +[assembly: InternalsVisibleTo("Unity.ML-Agents.Runtime.Utils.Tests")] + +namespace Unity.MLAgents +{ + internal static class GrpcExtensions + { +#if UNITY_EDITOR + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] + static void ResetStaticsOnLoad() + { + s_HaveWarnedTrainerCapabilitiesAgentGroup = false; + s_HaveWarnedTrainerCapabilitiesMultiPng = false; + s_HaveWarnedTrainerCapabilitiesMapping= false; + } +#endif + #region AgentInfo + /// + /// Static flag to make sure that we only fire the warning once. + /// + private static bool s_HaveWarnedTrainerCapabilitiesAgentGroup; + + /// + /// Converts a AgentInfo to a protobuf generated AgentInfoActionPairProto + /// + /// The protobuf version of the AgentInfoActionPairProto. + public static AgentInfoActionPairProto ToInfoActionPairProto(this AgentInfo ai) + { + var agentInfoProto = ai.ToAgentInfoProto(); + + var agentActionProto = new AgentActionProto(); + + if (!ai.storedActions.IsEmpty()) + { + if (!ai.storedActions.ContinuousActions.IsEmpty()) + { + agentActionProto.ContinuousActions.AddRange(ai.storedActions.ContinuousActions.Array); + } + if (!ai.storedActions.DiscreteActions.IsEmpty()) + { + agentActionProto.DiscreteActions.AddRange(ai.storedActions.DiscreteActions.Array); + } + } + + return new AgentInfoActionPairProto + { + AgentInfo = agentInfoProto, + ActionInfo = agentActionProto + }; + } + + /// + /// Converts a AgentInfo to a protobuf generated AgentInfoProto + /// + /// The protobuf version of the AgentInfo. + public static AgentInfoProto ToAgentInfoProto(this AgentInfo ai) + { + if (ai.groupId > 0) + { + var trainerCanHandle = Academy.Instance.TrainerCapabilities == null || Academy.Instance.TrainerCapabilities.MultiAgentGroups; + if (!trainerCanHandle) + { + if (!s_HaveWarnedTrainerCapabilitiesAgentGroup) + { + Debug.LogWarning( + $"Attached trainer doesn't support Multi Agent Groups; group rewards will be ignored." + + "Please find the versions that work best together from our release page: " + + "https://github.com/Unity-Technologies/ml-agents/releases" + ); + s_HaveWarnedTrainerCapabilitiesAgentGroup = true; + } + } + } + var agentInfoProto = new AgentInfoProto + { + Reward = ai.reward, + GroupReward = ai.groupReward, + MaxStepReached = ai.maxStepReached, + Done = ai.done, + Id = ai.episodeId, + GroupId = ai.groupId, + }; + + if (ai.discreteActionMasks != null) + { + agentInfoProto.ActionMask.AddRange(ai.discreteActionMasks); + } + + return agentInfoProto; + } + + /// + /// Get summaries for the observations in the AgentInfo part of the AgentInfoActionPairProto. + /// + /// + /// Summary of the observations. + public static List GetObservationSummaries(this AgentInfoActionPairProto infoActionPair) + { + List summariesOut = new List(); + var agentInfo = infoActionPair.AgentInfo; + foreach (var obs in agentInfo.Observations) + { + var summary = new ObservationSummary(); + summary.shape = obs.Shape.ToArray(); + summariesOut.Add(summary); + } + + return summariesOut; + } + + #endregion + + #region BrainParameters + /// + /// Converts a BrainParameters into to a BrainParametersProto so it can be sent. + /// + /// The BrainInfoProto generated. + /// The instance of BrainParameter to extend. + /// The name of the brain. + /// Whether or not the Brain is training. + public static BrainParametersProto ToProto(this BrainParameters bp, string name, bool isTraining) + { + // Disable deprecation warnings so we can set legacy fields +#pragma warning disable CS0618 + var brainParametersProto = new BrainParametersProto + { + VectorActionSpaceTypeDeprecated = (SpaceTypeProto)bp.VectorActionSpaceType, + BrainName = name, + IsTraining = isTraining, + ActionSpec = ToActionSpecProto(bp.ActionSpec), + }; + if (bp.VectorActionSize != null) + { + brainParametersProto.VectorActionSizeDeprecated.AddRange(bp.VectorActionSize); + } + if (bp.VectorActionDescriptions != null) + { + brainParametersProto.VectorActionDescriptionsDeprecated.AddRange(bp.VectorActionDescriptions); + } +#pragma warning restore CS0618 + return brainParametersProto; + } + + /// + /// Converts an ActionSpec into to a Protobuf BrainInfoProto so it can be sent. + /// + /// The BrainInfoProto generated. + /// Description of the actions for the Agent. + /// The name of the brain. + /// Whether or not the Brain is training. + public static BrainParametersProto ToBrainParametersProto(this ActionSpec actionSpec, string name, bool isTraining) + { + var brainParametersProto = new BrainParametersProto + { + BrainName = name, + IsTraining = isTraining, + ActionSpec = ToActionSpecProto(actionSpec), + }; + + var supportHybrid = Academy.Instance.TrainerCapabilities == null || Academy.Instance.TrainerCapabilities.HybridActions; + if (!supportHybrid) + { + actionSpec.CheckAllContinuousOrDiscrete(); + if (actionSpec.NumContinuousActions > 0) + { + brainParametersProto.VectorActionSizeDeprecated.Add(actionSpec.NumContinuousActions); + brainParametersProto.VectorActionSpaceTypeDeprecated = SpaceTypeProto.Continuous; + } + else if (actionSpec.NumDiscreteActions > 0) + { + brainParametersProto.VectorActionSizeDeprecated.AddRange(actionSpec.BranchSizes); + brainParametersProto.VectorActionSpaceTypeDeprecated = SpaceTypeProto.Discrete; + } + } + + // TODO handle ActionDescriptions? + return brainParametersProto; + } + + /// + /// Convert a BrainParametersProto to a BrainParameters struct. + /// + /// An instance of a brain parameters protobuf object. + /// A BrainParameters struct. + public static BrainParameters ToBrainParameters(this BrainParametersProto bpp) + { + ActionSpec actionSpec; + if (bpp.ActionSpec == null) + { + // Disable deprecation warnings so we can set legacy fields +#pragma warning disable CS0618 + var spaceType = (SpaceType)bpp.VectorActionSpaceTypeDeprecated; + if (spaceType == SpaceType.Continuous) + { + actionSpec = ActionSpec.MakeContinuous(bpp.VectorActionSizeDeprecated.ToArray()[0]); + } + else + { + actionSpec = ActionSpec.MakeDiscrete(bpp.VectorActionSizeDeprecated.ToArray()); + } +#pragma warning restore CS0618 + } + else + { + actionSpec = ToActionSpec(bpp.ActionSpec); + } + var bp = new BrainParameters + { + VectorActionDescriptions = bpp.VectorActionDescriptionsDeprecated.ToArray(), + ActionSpec = actionSpec, + }; + return bp; + } + + /// + /// Convert a ActionSpecProto to a ActionSpec struct. + /// + /// An instance of an action spec protobuf object. + /// An ActionSpec struct. + public static ActionSpec ToActionSpec(this ActionSpecProto actionSpecProto) + { + var actionSpec = new ActionSpec(actionSpecProto.NumContinuousActions); + if (actionSpecProto.DiscreteBranchSizes != null) + { + actionSpec.BranchSizes = actionSpecProto.DiscreteBranchSizes.ToArray(); + } + return actionSpec; + } + + /// + /// Convert a ActionSpec struct to a ActionSpecProto. + /// + /// An instance of an action spec struct. + /// An ActionSpecProto. + public static ActionSpecProto ToActionSpecProto(this ActionSpec actionSpec) + { + var actionSpecProto = new ActionSpecProto + { + NumContinuousActions = actionSpec.NumContinuousActions, + NumDiscreteActions = actionSpec.NumDiscreteActions, + }; + if (actionSpec.BranchSizes != null) + { + actionSpecProto.DiscreteBranchSizes.AddRange(actionSpec.BranchSizes); + } + return actionSpecProto; + } + + #endregion + + #region DemonstrationMetaData + /// + /// Convert metadata object to proto object. + /// + public static DemonstrationMetaProto ToProto(this DemonstrationMetaData dm) + { + var demonstrationName = dm.demonstrationName ?? ""; + var demoProto = new DemonstrationMetaProto + { + ApiVersion = DemonstrationMetaData.ApiVersion, + MeanReward = dm.meanReward, + NumberSteps = dm.numberSteps, + NumberEpisodes = dm.numberEpisodes, + DemonstrationName = demonstrationName + }; + return demoProto; + } + + /// + /// Initialize metadata values based on proto object. + /// + public static DemonstrationMetaData ToDemonstrationMetaData(this DemonstrationMetaProto demoProto) + { + var dm = new DemonstrationMetaData + { + numberEpisodes = demoProto.NumberEpisodes, + numberSteps = demoProto.NumberSteps, + meanReward = demoProto.MeanReward, + demonstrationName = demoProto.DemonstrationName + }; + if (demoProto.ApiVersion != DemonstrationMetaData.ApiVersion) + { + throw new Exception("API versions of demonstration are incompatible."); + } + return dm; + } + + #endregion + + public static UnityRLInitParameters ToUnityRLInitParameters(this UnityRLInitializationInputProto inputProto) + { + return new UnityRLInitParameters + { + seed = inputProto.Seed, + numAreas = inputProto.NumAreas, + pythonLibraryVersion = inputProto.PackageVersion, + pythonCommunicationVersion = inputProto.CommunicationVersion, + TrainerCapabilities = inputProto.Capabilities.ToRLCapabilities() + }; + } + + #region AgentAction + public static List ToAgentActionList(this UnityRLInputProto.Types.ListAgentActionProto proto) + { + var agentActions = new List(proto.Value.Count); + foreach (var ap in proto.Value) + { + agentActions.Add(ap.ToActionBuffers()); + } + return agentActions; + } + + public static ActionBuffers ToActionBuffers(this AgentActionProto proto) + { + return new ActionBuffers(proto.ContinuousActions.ToArray(), proto.DiscreteActions.ToArray()); + } + + #endregion + + #region Observations + /// + /// Static flag to make sure that we only fire the warning once. + /// + private static bool s_HaveWarnedTrainerCapabilitiesMultiPng; + private static bool s_HaveWarnedTrainerCapabilitiesMapping; + + /// + /// Generate an ObservationProto for the sensor using the provided ObservationWriter. + /// This is equivalent to producing an Observation and calling Observation.ToProto(), + /// but avoid some intermediate memory allocations. + /// + /// + /// + /// `ObservationProto` for the sensor. + public static ObservationProto GetObservationProto(this ISensor sensor, ObservationWriter observationWriter) + { + var obsSpec = sensor.GetObservationSpec(); + var shape = obsSpec.Shape; + ObservationProto observationProto = null; + var compressionSpec = sensor.GetCompressionSpec(); + var compressionType = compressionSpec.SensorCompressionType; + // Check capabilities if we need to concatenate PNGs + if (compressionType == SensorCompressionType.PNG && shape.Length == 3 && shape[0] > 3) + { + var trainerCanHandle = Academy.Instance.TrainerCapabilities == null || Academy.Instance.TrainerCapabilities.ConcatenatedPngObservations; + if (!trainerCanHandle) + { + if (!s_HaveWarnedTrainerCapabilitiesMultiPng) + { + Debug.LogWarning( + $"Attached trainer doesn't support multiple PNGs. Switching to uncompressed observations for sensor {sensor.GetName()}. " + + "Please find the versions that work best together from our release page: " + + "https://github.com/Unity-Technologies/ml-agents/releases" + ); + s_HaveWarnedTrainerCapabilitiesMultiPng = true; + } + compressionType = SensorCompressionType.None; + } + } + // Check capabilities if we need mapping for compressed observations + if (compressionType != SensorCompressionType.None && shape.Length == 3 && shape[0] > 3) + { + var trainerCanHandleMapping = Academy.Instance.TrainerCapabilities == null || Academy.Instance.TrainerCapabilities.CompressedChannelMapping; + var isTrivialMapping = compressionSpec.IsTrivialMapping(); + if (!trainerCanHandleMapping && !isTrivialMapping) + { + if (!s_HaveWarnedTrainerCapabilitiesMapping) + { + Debug.LogWarning( + $"The sensor {sensor.GetName()} is using non-trivial mapping and " + + "the attached trainer doesn't support compression mapping. " + + "Switching to uncompressed observations. " + + "Please find the versions that work best together from our release page: " + + "https://github.com/Unity-Technologies/ml-agents/releases" + ); + s_HaveWarnedTrainerCapabilitiesMapping = true; + } + compressionType = SensorCompressionType.None; + } + } + + if (compressionType == SensorCompressionType.None) + { + var numFloats = sensor.ObservationSize(); + var floatDataProto = new ObservationProto.Types.FloatData(); + // Resize the float array + // TODO upgrade protobuf versions so that we can set the Capacity directly - see https://github.com/protocolbuffers/protobuf/pull/6530 + for (var i = 0; i < numFloats; i++) + { + floatDataProto.Data.Add(0.0f); + } + + observationWriter.SetTarget(floatDataProto.Data, sensor.GetObservationSpec(), 0); + sensor.Write(observationWriter); + + observationProto = new ObservationProto + { + FloatData = floatDataProto, + CompressionType = (CompressionTypeProto)SensorCompressionType.None, + }; + } + else + { + var compressedObs = sensor.GetCompressedObservation(); + if (compressedObs == null) + { + throw new UnityAgentsException( + $"GetCompressedObservation() returned null data for sensor named {sensor.GetName()}. " + + "You must return a byte[]. If you don't want to use compressed observations, " + + "return CompressionSpec.Default() from GetCompressionSpec()." + ); + } + observationProto = new ObservationProto + { + CompressedData = ByteString.CopyFrom(compressedObs), + CompressionType = (CompressionTypeProto)sensor.GetCompressionSpec().SensorCompressionType, + }; + if (compressionSpec.CompressedChannelMapping != null) + { + observationProto.CompressedChannelMapping.AddRange(compressionSpec.CompressedChannelMapping); + } + } + + // Add the dimension properties to the observationProto + var dimensionProperties = obsSpec.DimensionProperties; + for (int i = 0; i < dimensionProperties.Length; i++) + { + observationProto.DimensionProperties.Add((int)dimensionProperties[i]); + } + + // Checking trainer compatibility with variable length observations + if (dimensionProperties == new InplaceArray(DimensionProperty.VariableSize, DimensionProperty.None)) + { + var trainerCanHandleVarLenObs = Academy.Instance.TrainerCapabilities == null || Academy.Instance.TrainerCapabilities.VariableLengthObservation; + if (!trainerCanHandleVarLenObs) + { + throw new UnityAgentsException("Variable Length Observations are not supported by the trainer"); + } + } + + for (var i = 0; i < shape.Length; i++) + { + observationProto.Shape.Add(shape[i]); + } + + var sensorName = sensor.GetName(); + if (!string.IsNullOrEmpty(sensorName)) + { + observationProto.Name = sensorName; + } + + observationProto.ObservationType = (ObservationTypeProto)obsSpec.ObservationType; + return observationProto; + } + + #endregion + + public static UnityRLCapabilities ToRLCapabilities(this UnityRLCapabilitiesProto proto) + { + return new UnityRLCapabilities + { + BaseRLCapabilities = proto.BaseRLCapabilities, + ConcatenatedPngObservations = proto.ConcatenatedPngObservations, + CompressedChannelMapping = proto.CompressedChannelMapping, + HybridActions = proto.HybridActions, + TrainingAnalytics = proto.TrainingAnalytics, + VariableLengthObservation = proto.VariableLengthObservation, + MultiAgentGroups = proto.MultiAgentGroups, + }; + } + + public static UnityRLCapabilitiesProto ToProto(this UnityRLCapabilities rlCaps) + { + return new UnityRLCapabilitiesProto + { + BaseRLCapabilities = rlCaps.BaseRLCapabilities, + ConcatenatedPngObservations = rlCaps.ConcatenatedPngObservations, + CompressedChannelMapping = rlCaps.CompressedChannelMapping, + HybridActions = rlCaps.HybridActions, + TrainingAnalytics = rlCaps.TrainingAnalytics, + VariableLengthObservation = rlCaps.VariableLengthObservation, + MultiAgentGroups = rlCaps.MultiAgentGroups, + }; + } + + #region Analytics + internal static TrainingEnvironmentInitializedEvent ToTrainingEnvironmentInitializedEvent( + this TrainingEnvironmentInitialized inputProto) + { + return new TrainingEnvironmentInitializedEvent + { + TrainerPythonVersion = inputProto.PythonVersion, + MLAgentsVersion = inputProto.MlagentsVersion, + MLAgentsEnvsVersion = inputProto.MlagentsEnvsVersion, + TorchVersion = inputProto.TorchVersion, + TorchDeviceType = inputProto.TorchDeviceType, + NumEnvironments = inputProto.NumEnvs, + NumEnvironmentParameters = inputProto.NumEnvironmentParameters, + RunOptions = inputProto.RunOptions, + }; + } + + internal static TrainingBehaviorInitializedEvent ToTrainingBehaviorInitializedEvent( + this TrainingBehaviorInitialized inputProto) + { + RewardSignals rewardSignals = 0; + rewardSignals |= inputProto.ExtrinsicRewardEnabled ? RewardSignals.Extrinsic : 0; + rewardSignals |= inputProto.GailRewardEnabled ? RewardSignals.Gail : 0; + rewardSignals |= inputProto.CuriosityRewardEnabled ? RewardSignals.Curiosity : 0; + rewardSignals |= inputProto.RndRewardEnabled ? RewardSignals.Rnd : 0; + + TrainingFeatures trainingFeatures = 0; + trainingFeatures |= inputProto.BehavioralCloningEnabled ? TrainingFeatures.BehavioralCloning : 0; + trainingFeatures |= inputProto.RecurrentEnabled ? TrainingFeatures.Recurrent : 0; + trainingFeatures |= inputProto.TrainerThreaded ? TrainingFeatures.Threaded : 0; + trainingFeatures |= inputProto.SelfPlayEnabled ? TrainingFeatures.SelfPlay : 0; + trainingFeatures |= inputProto.CurriculumEnabled ? TrainingFeatures.Curriculum : 0; + + + return new TrainingBehaviorInitializedEvent + { + BehaviorName = inputProto.BehaviorName, + TrainerType = inputProto.TrainerType, + RewardSignalFlags = rewardSignals, + TrainingFeatureFlags = trainingFeatures, + VisualEncoder = inputProto.VisualEncoder, + NumNetworkLayers = inputProto.NumNetworkLayers, + NumNetworkHiddenUnits = inputProto.NumNetworkHiddenUnits, + Config = inputProto.Config, + }; + } + + #endregion + } +} diff --git a/com.unity.ml-agents/Runtime/Communicator/GrpcExtensions.cs.meta b/com.unity.ml-agents/Runtime/Communicator/GrpcExtensions.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..31c109f8fac95b9b7632245e730c7582e27bc913 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Communicator/GrpcExtensions.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Communicator/ICommunicator.cs b/com.unity.ml-agents/Runtime/Communicator/ICommunicator.cs new file mode 100644 index 0000000000000000000000000000000000000000..c39281990d13f9cad704d3972dd91e8f8ab5b6dd --- /dev/null +++ b/com.unity.ml-agents/Runtime/Communicator/ICommunicator.cs @@ -0,0 +1,181 @@ +using System; +using System.Collections.Generic; +using Unity.MLAgents.Actuators; +using Unity.MLAgents.Sensors; + +namespace Unity.MLAgents +{ + /// + /// Communicator initialization parameters. + /// + public struct CommunicatorInitParameters + { + /// + /// Port to listen for connections on. + /// + public int port; + + /// + /// The name of the environment. + /// + public string name; + + /// + /// The version of the Unity SDK. + /// + public string unityPackageVersion; + + /// + /// The version of the communication API. + /// + public string unityCommunicationVersion; + + /// + /// The RL capabilities of the C# codebase. + /// + public UnityRLCapabilities CSharpCapabilities; + } + + /// + /// Initialization parameters for the Unity environment. + /// + public struct UnityRLInitParameters + { + /// + /// A random number generator (RNG) seed sent from the python process to Unity. + /// + public int seed; + + /// + /// The number of areas to replicate if Training Area Replication is used in the scene. + /// + public int numAreas; + + /// + /// The library version of the python process. + /// + public string pythonLibraryVersion; + + /// + /// The version of the communication API that python is using. + /// + public string pythonCommunicationVersion; + + /// + /// The RL capabilities of the Trainer codebase. + /// + public UnityRLCapabilities TrainerCapabilities; + } + internal struct UnityRLInputParameters + { + /// + /// Boolean sent back from python to indicate whether or not training is happening. + /// + public bool isTraining; + } + + /// + /// Delegate for handling quit events sent back from the communicator. + /// + public delegate void QuitCommandHandler(); + + /// + /// Delegate for handling reset parameter updates sent from the communicator. + /// + public delegate void ResetCommandHandler(); + + /// + /// Delegate to handle UnityRLInputParameters updates from the communicator. + /// + /// + internal delegate void RLInputReceivedHandler(UnityRLInputParameters inputParams); + + /// + /// This is the interface of the Communicators. + /// This does not need to be modified nor implemented to create a Unity environment. + /// + /// When the Unity Communicator is initialized, it will wait for the External Communicator + /// to be initialized as well. The two communicators will then exchange their first messages + /// that will usually contain information for initialization (information that does not need + /// to be resent at each new exchange). + /// + /// By convention a Unity input is from External to Unity and a Unity output is from Unity to + /// External. Inputs and outputs are relative to Unity. + /// + /// By convention, when the Unity Communicator and External Communicator call exchange, the + /// exchange is NOT simultaneous but sequential. This means that when a side of the + /// communication calls exchange, the other will receive the result of its previous + /// xchange call. + /// This is what happens when A calls exchange a single time: + /// A sends data_1 to B -> B receives data_1 -> B generates and sends data_2 -> A receives data_2 + /// When A calls exchange, it sends data_1 and receives data_2 + /// + /// Since the messages are sent back and forth with exchange and simultaneously when calling + /// initialize, External sends two messages at initialization. + /// + /// The structure of the messages is as follows: + /// UnityMessage + /// ...Header + /// ...UnityOutput + /// ......UnityRLOutput + /// ......UnityRLInitializationOutput + /// ...UnityInput + /// ......UnityRLInput + /// ......UnityRLInitializationInput + /// + /// UnityOutput and UnityInput can be extended to provide functionalities beyond RL + /// UnityRLOutput and UnityRLInput can be extended to provide new RL functionalities + /// + /// + public interface ICommunicator : IDisposable + { + /// + /// Quit was received by the communicator. + /// + event QuitCommandHandler QuitCommandReceived; + + /// + /// Reset command sent back from the communicator. + /// + event ResetCommandHandler ResetCommandReceived; + + /// + /// Sends the academy parameters through the Communicator. + /// Is used by the academy to send the AcademyParameters to the communicator. + /// + /// Whether the connection was successful. + /// The Unity Initialization Parameters to be sent. + /// The External Initialization Parameters received + bool Initialize(CommunicatorInitParameters initParameters, out UnityRLInitParameters initParametersOut); + + /// + /// Registers a new Brain to the Communicator. + /// + /// The name or key uniquely identifying the Brain. + /// Description of the actions for the Agent. + void SubscribeBrain(string name, ActionSpec actionSpec); + + /// + /// Sends the observations of one Agent. + /// + /// Batch Key. + /// Agent info. + /// The list of ISensors of the Agent. + void PutObservations(string brainKey, AgentInfo info, List sensors); + + /// + /// Signals the ICommunicator that the Agents are now ready to receive their action + /// and that if the communicator has not yet received an action for one of the Agents + /// it needs to get one at this point. + /// + void DecideBatch(); + + /// + /// Gets the AgentActions based on the batching key. + /// + /// A key to identify which behavior actions to get. + /// A key to identify which Agent actions to get. + /// `ActionBuffers` corresponding to the input key. + ActionBuffers GetActions(string key, int agentId); + } +} diff --git a/com.unity.ml-agents/Runtime/Communicator/ICommunicator.cs.meta b/com.unity.ml-agents/Runtime/Communicator/ICommunicator.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..15f8a01eb313b7068c1348fc599dde23c4b090b4 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Communicator/ICommunicator.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Communicator/RpcCommunicator.cs b/com.unity.ml-agents/Runtime/Communicator/RpcCommunicator.cs new file mode 100644 index 0000000000000000000000000000000000000000..41fcae7e1565268264349f049047bae21c7e186c --- /dev/null +++ b/com.unity.ml-agents/Runtime/Communicator/RpcCommunicator.cs @@ -0,0 +1,607 @@ +#if UNITY_EDITOR || UNITY_STANDALONE +#define MLA_SUPPORTED_TRAINING_PLATFORM +#endif + +#if MLA_SUPPORTED_TRAINING_PLATFORM +using Grpc.Core; +#if UNITY_EDITOR +using UnityEditor; +#endif +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using Unity.MLAgents.Actuators; +using Unity.MLAgents.CommunicatorObjects; +using Unity.MLAgents.Sensors; +using Unity.MLAgents.SideChannels; +using Google.Protobuf; + +using Unity.MLAgents.Analytics; + +namespace Unity.MLAgents +{ + /// Responsible for communication with External using gRPC. + public class RpcCommunicator : ICommunicator + { + public event QuitCommandHandler QuitCommandReceived; + public event ResetCommandHandler ResetCommandReceived; + + /// If true, the communication is active. + bool m_IsOpen; + + List m_BehaviorNames = new List(); + bool m_NeedCommunicateThisStep; + ObservationWriter m_ObservationWriter = new ObservationWriter(); + Dictionary m_SensorShapeValidators = new Dictionary(); + Dictionary> m_OrderedAgentsRequestingDecisions = new Dictionary>(); + + /// The current UnityRLOutput to be sent when all the brains queried the communicator + UnityRLOutputProto m_CurrentUnityRlOutput = + new UnityRLOutputProto(); + + Dictionary> m_LastActionsReceived = + new Dictionary>(); + + // Brains that we have sent over the communicator with agents. + HashSet m_SentBrainKeys = new HashSet(); + Dictionary m_UnsentBrainKeys = new Dictionary(); + + + /// The Unity to External client. + UnityToExternalProto.UnityToExternalProtoClient m_Client; + Channel m_Channel; + + /// + /// Initializes a new instance of the RPCCommunicator class. + /// + protected RpcCommunicator() + { + } + + public static RpcCommunicator Create() + { +#if MLA_SUPPORTED_TRAINING_PLATFORM + return new RpcCommunicator(); +#else + return null; +#endif + } + + #region Initialization + + internal static bool CheckCommunicationVersionsAreCompatible( + string unityCommunicationVersion, + string pythonApiVersion + ) + { + var unityVersion = new Version(unityCommunicationVersion); + var pythonVersion = new Version(pythonApiVersion); + if (unityVersion.Major == 0) + { + if (unityVersion.Major != pythonVersion.Major || unityVersion.Minor != pythonVersion.Minor) + { + return false; + } + } + else if (unityVersion.Major != pythonVersion.Major) + { + return false; + } + else if (unityVersion.Minor != pythonVersion.Minor) + { + // If a feature is used in Unity but not supported in the trainer, + // we will warn at the point it's used. Don't warn here to avoid noise. + } + return true; + } + + /// + /// Sends the initialization parameters through the Communicator. + /// Is used by the academy to send initialization parameters to the communicator. + /// + /// Whether the connection was successful. + /// The Unity Initialization Parameters to be sent. + /// The External Initialization Parameters received. + public bool Initialize(CommunicatorInitParameters initParameters, out UnityRLInitParameters initParametersOut) + { +#if MLA_SUPPORTED_TRAINING_PLATFORM + var academyParameters = new UnityRLInitializationOutputProto + { + Name = initParameters.name, + PackageVersion = initParameters.unityPackageVersion, + CommunicationVersion = initParameters.unityCommunicationVersion, + Capabilities = initParameters.CSharpCapabilities.ToProto() + }; + + UnityInputProto input; + UnityInputProto initializationInput; + try + { + initializationInput = Initialize( + initParameters.port, + new UnityOutputProto + { + RlInitializationOutput = academyParameters + }, + out input + ); + } + catch (Exception ex) + { + if (ex is RpcException rpcException) + { + switch (rpcException.Status.StatusCode) + { + case StatusCode.Unavailable: + // This is the common case where there's no trainer to connect to. + break; + case StatusCode.DeadlineExceeded: + // We don't currently set a deadline for connection, but likely will in the future. + break; + default: + Debug.Log($"Unexpected gRPC exception when trying to initialize communication: {rpcException}"); + break; + } + } + else + { + Debug.Log($"Unexpected exception when trying to initialize communication: {ex}"); + } + initParametersOut = new UnityRLInitParameters(); + NotifyQuitAndShutDownChannel(); + return false; + } + + var pythonPackageVersion = initializationInput.RlInitializationInput.PackageVersion; + var pythonCommunicationVersion = initializationInput.RlInitializationInput.CommunicationVersion; + TrainingAnalytics.SetTrainerInformation(pythonPackageVersion, pythonCommunicationVersion); + + var communicationIsCompatible = CheckCommunicationVersionsAreCompatible( + initParameters.unityCommunicationVersion, + pythonCommunicationVersion + ); + + // Initialization succeeded part-way. The most likely cause is a mismatch between the communicator + // API strings, so log an explicit warning if that's the case. + if (initializationInput != null && input == null) + { + if (!communicationIsCompatible) + { + Debug.LogWarningFormat( + "Communication protocol between python ({0}) and Unity ({1}) have different " + + "versions which make them incompatible. Python library version: {2}.", + pythonCommunicationVersion, initParameters.unityCommunicationVersion, + pythonPackageVersion + ); + } + else + { + Debug.LogWarningFormat( + "Unknown communication error between Python. Python communication protocol: {0}, " + + "Python library version: {1}.", + pythonCommunicationVersion, + pythonPackageVersion + ); + } + + initParametersOut = new UnityRLInitParameters(); + return false; + } + + UpdateEnvironmentWithInput(input.RlInput); + initParametersOut = initializationInput.RlInitializationInput.ToUnityRLInitParameters(); + // Be sure to shut down the grpc channel when the application is quitting. + Application.quitting += NotifyQuitAndShutDownChannel; + return true; +#else + initParametersOut = new UnityRLInitParameters(); + return false; +#endif + } + + /// + /// Adds the brain to the list of brains which will be sending information to External. + /// + /// Brain key. + /// Description of the actions for the Agent. + public void SubscribeBrain(string brainKey, ActionSpec actionSpec) + { + if (m_BehaviorNames.Contains(brainKey)) + { + return; + } + m_BehaviorNames.Add(brainKey); + m_CurrentUnityRlOutput.AgentInfos.Add( + brainKey, + new UnityRLOutputProto.Types.ListAgentInfoProto() + ); + + CacheActionSpec(brainKey, actionSpec); + } + + void UpdateEnvironmentWithInput(UnityRLInputProto rlInput) + { + SideChannelManager.ProcessSideChannelData(rlInput.SideChannel.ToArray()); + SendCommandEvent(rlInput.Command); + } + + UnityInputProto Initialize(int port, UnityOutputProto unityOutput, out UnityInputProto unityInput) + { + m_IsOpen = true; + m_Channel = new Channel($"localhost:{port}", ChannelCredentials.Insecure); + + m_Client = new UnityToExternalProto.UnityToExternalProtoClient(m_Channel); + var result = m_Client.Exchange(WrapMessage(unityOutput, 200)); + var inputMessage = m_Client.Exchange(WrapMessage(null, 200)); + unityInput = inputMessage.UnityInput; +#if UNITY_EDITOR + EditorApplication.playModeStateChanged += HandleOnPlayModeChanged; +#endif + if (result.Header.Status != 200 || inputMessage.Header.Status != 200) + { + m_IsOpen = false; + NotifyQuitAndShutDownChannel(); + } + return result.UnityInput; + } + + void NotifyQuitAndShutDownChannel() + { + QuitCommandReceived?.Invoke(); + try + { + m_Channel.ShutdownAsync().Wait(); + } + catch (Exception) + { + // do nothing + } + } + + #endregion + + #region Destruction + + /// + /// Close the communicator gracefully on both sides of the communication. + /// + public void Dispose() + { + if (!m_IsOpen) + { + return; + } + + try + { + m_Client.Exchange(WrapMessage(null, 400)); + m_IsOpen = false; + } + catch + { + // ignored + } + } + + #endregion + + #region Sending Events + + void SendCommandEvent(CommandProto command) + { + switch (command) + { + case CommandProto.Quit: + { + NotifyQuitAndShutDownChannel(); + return; + } + case CommandProto.Reset: + { + foreach (var brainName in m_OrderedAgentsRequestingDecisions.Keys) + { + m_OrderedAgentsRequestingDecisions[brainName].Clear(); + } + ResetCommandReceived?.Invoke(); + return; + } + default: + { + return; + } + } + } + + #endregion + + #region Sending and retreiving data + + public void DecideBatch() + { + if (!m_NeedCommunicateThisStep) + { + return; + } + m_NeedCommunicateThisStep = false; + + SendBatchedMessageHelper(); + } + + /// + /// Sends the observations of one Agent. + /// + /// Batch Key. + /// Agent info. + /// Sensors that will produce the observations + public void PutObservations(string behaviorName, AgentInfo info, List sensors) + { +#if DEBUG + if (!m_SensorShapeValidators.ContainsKey(behaviorName)) + { + m_SensorShapeValidators[behaviorName] = new SensorShapeValidator(); + } + m_SensorShapeValidators[behaviorName].ValidateSensors(sensors); +#endif + + using (TimerStack.Instance.Scoped("AgentInfo.ToProto")) + { + var agentInfoProto = info.ToAgentInfoProto(); + + using (TimerStack.Instance.Scoped("GenerateSensorData")) + { + foreach (var sensor in sensors) + { + var obsProto = sensor.GetObservationProto(m_ObservationWriter); + agentInfoProto.Observations.Add(obsProto); + } + } + m_CurrentUnityRlOutput.AgentInfos[behaviorName].Value.Add(agentInfoProto); + } + + m_NeedCommunicateThisStep = true; + if (!m_OrderedAgentsRequestingDecisions.ContainsKey(behaviorName)) + { + m_OrderedAgentsRequestingDecisions[behaviorName] = new List(); + } + if (!info.done) + { + m_OrderedAgentsRequestingDecisions[behaviorName].Add(info.episodeId); + } + if (!m_LastActionsReceived.ContainsKey(behaviorName)) + { + m_LastActionsReceived[behaviorName] = new Dictionary(); + } + m_LastActionsReceived[behaviorName][info.episodeId] = ActionBuffers.Empty; + if (info.done) + { + m_LastActionsReceived[behaviorName].Remove(info.episodeId); + } + } + + /// + /// Helper method that sends the current UnityRLOutput, receives the next UnityInput and + /// Applies the appropriate AgentAction to the agents. + /// + void SendBatchedMessageHelper() + { + var message = new UnityOutputProto + { + RlOutput = m_CurrentUnityRlOutput, + }; + var tempUnityRlInitializationOutput = GetTempUnityRlInitializationOutput(); + if (tempUnityRlInitializationOutput != null) + { + message.RlInitializationOutput = tempUnityRlInitializationOutput; + } + + byte[] messageAggregated = SideChannelManager.GetSideChannelMessage(); + message.RlOutput.SideChannel = ByteString.CopyFrom(messageAggregated); + + var input = Exchange(message); + UpdateSentActionSpec(tempUnityRlInitializationOutput); + + foreach (var k in m_CurrentUnityRlOutput.AgentInfos.Keys) + { + m_CurrentUnityRlOutput.AgentInfos[k].Value.Clear(); + } + + var rlInput = input?.RlInput; + + if (rlInput?.AgentActions == null) + { + return; + } + + UpdateEnvironmentWithInput(rlInput); + + foreach (var brainName in rlInput.AgentActions.Keys) + { + if (!m_OrderedAgentsRequestingDecisions[brainName].Any()) + { + continue; + } + + if (!rlInput.AgentActions[brainName].Value.Any()) + { + continue; + } + + var agentActions = rlInput.AgentActions[brainName].ToAgentActionList(); + var numAgents = m_OrderedAgentsRequestingDecisions[brainName].Count; + for (var i = 0; i < numAgents; i++) + { + var agentAction = agentActions[i]; + var agentId = m_OrderedAgentsRequestingDecisions[brainName][i]; + if (m_LastActionsReceived[brainName].ContainsKey(agentId)) + { + m_LastActionsReceived[brainName][agentId] = agentAction; + } + } + } + foreach (var brainName in m_OrderedAgentsRequestingDecisions.Keys) + { + m_OrderedAgentsRequestingDecisions[brainName].Clear(); + } + } + + public ActionBuffers GetActions(string behaviorName, int agentId) + { + if (m_LastActionsReceived.ContainsKey(behaviorName)) + { + if (m_LastActionsReceived[behaviorName].ContainsKey(agentId)) + { + return m_LastActionsReceived[behaviorName][agentId]; + } + } + return ActionBuffers.Empty; + } + + /// + /// Send a UnityOutput and receives a UnityInput. + /// + /// The next UnityInput. + /// The UnityOutput to be sent. + UnityInputProto Exchange(UnityOutputProto unityOutput) + { + if (!m_IsOpen) + { + return null; + } + + try + { + var message = m_Client.Exchange(WrapMessage(unityOutput, 200)); + if (message.Header.Status == 200) + { + return message.UnityInput; + } + + m_IsOpen = false; + // Not sure if the quit command is actually sent when a + // non 200 message is received. Notify that we are indeed + // quitting. + NotifyQuitAndShutDownChannel(); + return message.UnityInput; + } + catch (Exception ex) + { + if (ex is RpcException rpcException) + { + // Log more verbose errors if they're something the user can possibly do something about. + switch (rpcException.Status.StatusCode) + { + case StatusCode.Unavailable: + // This can happen when python disconnects. Ignore it to avoid noisy logs. + break; + case StatusCode.ResourceExhausted: + // This happens is the message body is too large. There's no way to + // gracefully handle this, but at least we can show the message and the + // user can try to reduce the number of agents or observation sizes. + Debug.LogError($"GRPC Exception: {rpcException.Message}. Disconnecting from trainer."); + break; + default: + // Other unknown errors. Log at INFO level. + Debug.Log($"GRPC Exception: {rpcException.Message}. Disconnecting from trainer."); + break; + } + } + else + { + // Fall-through for other error types + Debug.LogError($"Communication Exception: {ex.Message}. Disconnecting from trainer."); + } + + m_IsOpen = false; + NotifyQuitAndShutDownChannel(); + return null; + } + } + + /// + /// Wraps the UnityOutput into a message with the appropriate status. + /// + /// The UnityMessage corresponding. + /// The UnityOutput to be wrapped. + /// The status of the message. + static UnityMessageProto WrapMessage(UnityOutputProto content, int status) + { + return new UnityMessageProto + { + Header = new HeaderProto { Status = status }, + UnityOutput = content + }; + } + + void CacheActionSpec(string behaviorName, ActionSpec actionSpec) + { + if (m_SentBrainKeys.Contains(behaviorName)) + { + return; + } + + // TODO We should check that if m_unsentBrainKeys has brainKey, it equals actionSpec + m_UnsentBrainKeys[behaviorName] = actionSpec; + } + + UnityRLInitializationOutputProto GetTempUnityRlInitializationOutput() + { + UnityRLInitializationOutputProto output = null; + foreach (var behaviorName in m_UnsentBrainKeys.Keys) + { + if (m_CurrentUnityRlOutput.AgentInfos.ContainsKey(behaviorName)) + { + if (m_CurrentUnityRlOutput.AgentInfos[behaviorName].CalculateSize() > 0) + { + // Only send the actionSpec if there is a non empty list of + // AgentInfos ready to be sent. + // This is to ensure that The Python side will always have a first + // observation when receiving the ActionSpec + if (output == null) + { + output = new UnityRLInitializationOutputProto(); + } + + var actionSpec = m_UnsentBrainKeys[behaviorName]; + output.BrainParameters.Add(actionSpec.ToBrainParametersProto(behaviorName, true)); + } + } + } + + return output; + } + + void UpdateSentActionSpec(UnityRLInitializationOutputProto output) + { + if (output == null) + { + return; + } + + foreach (var brainProto in output.BrainParameters) + { + m_SentBrainKeys.Add(brainProto.BrainName); + m_UnsentBrainKeys.Remove(brainProto.BrainName); + } + } + + #endregion + +#if UNITY_EDITOR + /// + /// When the editor exits, the communicator must be closed + /// + /// State. + void HandleOnPlayModeChanged(PlayModeStateChange state) + { + // This method is run whenever the playmode state is changed. + if (state == PlayModeStateChange.ExitingPlayMode) + { + Dispose(); + } + } + +#endif + } +} +#endif // UNITY_EDITOR || UNITY_STANDALONE diff --git a/com.unity.ml-agents/Runtime/Communicator/RpcCommunicator.cs.meta b/com.unity.ml-agents/Runtime/Communicator/RpcCommunicator.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..d1903d74ccd9b9f6eaa4fd9ad5a34a01cd3ce963 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Communicator/RpcCommunicator.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Communicator/UnityRLCapabilities.cs b/com.unity.ml-agents/Runtime/Communicator/UnityRLCapabilities.cs new file mode 100644 index 0000000000000000000000000000000000000000..af91dcd771a89c5a68d616ea10814b335bf4e368 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Communicator/UnityRLCapabilities.cs @@ -0,0 +1,91 @@ +using UnityEngine; + +namespace Unity.MLAgents +{ + /// + /// A class holding the capabilities flags for Reinforcement Learning across C# and the Trainer codebase. + /// + public class UnityRLCapabilities + { + /// + /// Base RL capabilities. + /// + public bool BaseRLCapabilities; + + /// + /// Concatenated PNG observations. + /// + public bool ConcatenatedPngObservations; + + /// + /// Compressed channel mapping. + /// + public bool CompressedChannelMapping; + + /// + /// Hybrid actions. + /// + public bool HybridActions; + + /// + /// Training analytics. + /// + public bool TrainingAnalytics; + + /// + /// Variable length observation. + /// + public bool VariableLengthObservation; + + /// + /// Multi-agent groups. + /// + public bool MultiAgentGroups; + + /// + /// A class holding the capabilities flags for Reinforcement Learning across C# and the Trainer codebase. This + /// struct will be used to inform users if and when they are using C# / Trainer features that are mismatched. + /// + /// Base RL capabilities. + /// Concatenated PNG observations. + /// Compressed channel mapping. + /// Hybrid actions. + /// Training analytics. + /// Variable length observation. + /// Multi-agent groups. + public UnityRLCapabilities( + bool baseRlCapabilities = true, + bool concatenatedPngObservations = true, + bool compressedChannelMapping = true, + bool hybridActions = true, + bool trainingAnalytics = true, + bool variableLengthObservation = true, + bool multiAgentGroups = true) + { + BaseRLCapabilities = baseRlCapabilities; + ConcatenatedPngObservations = concatenatedPngObservations; + CompressedChannelMapping = compressedChannelMapping; + HybridActions = hybridActions; + TrainingAnalytics = trainingAnalytics; + VariableLengthObservation = variableLengthObservation; + MultiAgentGroups = multiAgentGroups; + } + + /// + /// Will print a warning to the console if Python does not support base capabilities and will + /// return true if the warning was printed. + /// + /// True if the warning was printed, False if not. + public bool WarnOnPythonMissingBaseRLCapabilities() + { + if (BaseRLCapabilities) + { + return false; + } + Debug.LogWarning("Unity has connected to a Training process that does not support" + + "Base Reinforcement Learning Capabilities. Please make sure you have the" + + " latest training codebase installed for this version of the ML-Agents package."); + return true; + } + } +} diff --git a/com.unity.ml-agents/Runtime/Communicator/UnityRLCapabilities.cs.meta b/com.unity.ml-agents/Runtime/Communicator/UnityRLCapabilities.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..6cdc57628e5e02a1a4970ea394b83d9db567d99c Binary files /dev/null and b/com.unity.ml-agents/Runtime/Communicator/UnityRLCapabilities.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Constants.cs b/com.unity.ml-agents/Runtime/Constants.cs new file mode 100644 index 0000000000000000000000000000000000000000..4be9eba0428483470308d6cc53490bdafd1cb3d8 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Constants.cs @@ -0,0 +1,12 @@ +namespace Unity.MLAgents +{ + /// + /// Grouping for use in AddComponentMenu (instead of nesting the menus). + /// + internal enum MenuGroup + { + Default = 0, + Sensors = 50, + Actuators = 100 + } +} diff --git a/com.unity.ml-agents/Runtime/Constants.cs.meta b/com.unity.ml-agents/Runtime/Constants.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..f963ba55aafb949feec1cb88842f73dd58e5cd0b Binary files /dev/null and b/com.unity.ml-agents/Runtime/Constants.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/DecisionRequester.cs b/com.unity.ml-agents/Runtime/DecisionRequester.cs new file mode 100644 index 0000000000000000000000000000000000000000..178c9c08645175993875b2bbe0bf2585333352ba --- /dev/null +++ b/com.unity.ml-agents/Runtime/DecisionRequester.cs @@ -0,0 +1,135 @@ +using System; +using UnityEngine; +using UnityEngine.Serialization; + +namespace Unity.MLAgents +{ + /// + /// The DecisionRequester component automatically request decisions for an + /// instance at regular intervals. + /// + /// + /// Attach a DecisionRequester component to the same [GameObject] as the + /// component. + /// + /// The DecisionRequester component provides a convenient and flexible way to + /// trigger the agent decision making process. Without a DecisionRequester, + /// your implementation must manually call its + /// function. + /// + [AddComponentMenu("ML Agents/Decision Requester", (int)MenuGroup.Default)] + [RequireComponent(typeof(Agent))] + [DefaultExecutionOrder(-10)] + public class DecisionRequester : MonoBehaviour + { + /// + /// The frequency with which the agent requests a decision. A DecisionPeriod of 5 means + /// that the Agent will request a decision every 5 Academy steps. /// + [Range(1, 20)] + [Tooltip("The frequency with which the agent requests a decision. A DecisionPeriod " + + "of 5 means that the Agent will request a decision every 5 Academy steps.")] + public int DecisionPeriod = 5; + + /// + /// Indicates when to requests a decision. By changing this value, the timing of decision + /// can be shifted even among agents with the same decision period. The value can be + /// from 0 to DecisionPeriod - 1. + /// + [Range(0, 19)] + [Tooltip("Indicates when to requests a decision. By changing this value, the timing " + + "of decision can be shifted even among agents with the same decision period. " + + "The value can be from 0 to DecisionPeriod - 1.")] + public int DecisionStep = 0; + + /// + /// Indicates whether or not the agent will take an action during the Academy steps where + /// it does not request a decision. Has no effect when DecisionPeriod is set to 1. + /// + [Tooltip("Indicates whether or not the agent will take an action during the Academy " + + "steps where it does not request a decision. Has no effect when DecisionPeriod " + + "is set to 1.")] + [FormerlySerializedAs("RepeatAction")] + public bool TakeActionsBetweenDecisions = true; + + [NonSerialized] + Agent m_Agent; + + /// + /// Get the Agent attached to the DecisionRequester. + /// + public Agent Agent + { + get => m_Agent; + } + + internal void Awake() + { + Debug.Assert(DecisionStep < DecisionPeriod, "DecisionStep must be between 0 and DecisionPeriod - 1."); + m_Agent = gameObject.GetComponent(); + Debug.Assert(m_Agent != null, "Agent component was not found on this gameObject and is required."); + Academy.Instance.AgentPreStep += MakeRequests; + } + + void OnDestroy() + { + if (Academy.IsInitialized) + { + Academy.Instance.AgentPreStep -= MakeRequests; + } + } + + /// + /// Information about Academy step used to make decisions about whether to request a decision. + /// + public struct DecisionRequestContext + { + /// + /// The current step count of the Academy, equivalent to Academy.StepCount. + /// + public int AcademyStepCount; + } + + /// + /// Method that hooks into the Academy in order inform the Agent on whether or not it should request a + /// decision, and whether or not it should take actions between decisions. + /// + /// The current step count of the academy. + void MakeRequests(int academyStepCount) + { + var context = new DecisionRequestContext + { + AcademyStepCount = academyStepCount + }; + + if (ShouldRequestDecision(context)) + { + m_Agent?.RequestDecision(); + } + + if (ShouldRequestAction(context)) + { + m_Agent?.RequestAction(); + } + } + + /// + /// Whether Agent.RequestDecision should be called on this update step. + /// + /// `RequestDecision` context. + /// True if the agent `RequestDecision` should be called on this update step, False if not. + protected virtual bool ShouldRequestDecision(DecisionRequestContext context) + { + return context.AcademyStepCount % DecisionPeriod == DecisionStep; + } + + /// + /// Whether Agent.RequestAction should be called on this update step. + /// + /// `RequestDecision` context. + /// True if the agent `RequestAction` should be called on this update step, False if not. + protected virtual bool ShouldRequestAction(DecisionRequestContext context) + { + return TakeActionsBetweenDecisions; + } + } +} diff --git a/com.unity.ml-agents/Runtime/DecisionRequester.cs.meta b/com.unity.ml-agents/Runtime/DecisionRequester.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..bdc416b94bdd180da99f74031932083059c5ed8e Binary files /dev/null and b/com.unity.ml-agents/Runtime/DecisionRequester.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Demonstrations.meta b/com.unity.ml-agents/Runtime/Demonstrations.meta new file mode 100644 index 0000000000000000000000000000000000000000..85288b5325dacbb54270ab23fc6d6b4092336702 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Demonstrations.meta differ diff --git a/com.unity.ml-agents/Runtime/Demonstrations/DemonstrationMetaData.cs b/com.unity.ml-agents/Runtime/Demonstrations/DemonstrationMetaData.cs new file mode 100644 index 0000000000000000000000000000000000000000..42f67733df995cfee9b46719fef9212210628e42 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Demonstrations/DemonstrationMetaData.cs @@ -0,0 +1,20 @@ +using System; +using UnityEngine.Serialization; + +namespace Unity.MLAgents.Demonstrations +{ + /// + /// Demonstration meta-data. + /// Kept in a struct for easy serialization and deserialization. + /// + [Serializable] + internal class DemonstrationMetaData + { + [FormerlySerializedAs("numberExperiences")] + public int numberSteps; + public int numberEpisodes; + public float meanReward; + public string demonstrationName; + public const int ApiVersion = 1; + } +} diff --git a/com.unity.ml-agents/Runtime/Demonstrations/DemonstrationMetaData.cs.meta b/com.unity.ml-agents/Runtime/Demonstrations/DemonstrationMetaData.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..8e6ff392755eae0586f91f8eba29e1c5bfcc8b05 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Demonstrations/DemonstrationMetaData.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Demonstrations/DemonstrationRecorder.cs b/com.unity.ml-agents/Runtime/Demonstrations/DemonstrationRecorder.cs new file mode 100644 index 0000000000000000000000000000000000000000..a7f76278980565f7a55e4917644730ef726ce936 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Demonstrations/DemonstrationRecorder.cs @@ -0,0 +1,228 @@ +using System.IO.Abstractions; +using System.Text.RegularExpressions; +using UnityEngine; +using System.IO; +using Unity.MLAgents.Policies; +using UnityEngine.Serialization; + +namespace Unity.MLAgents.Demonstrations +{ + /// + /// The Demonstration Recorder component facilitates the recording of demonstrations + /// used for imitation learning. + /// + /// Add this component to the [GameObject] containing an + /// to enable recording the agent for imitation learning. You must implement the + /// function of the agent to provide manual control + /// in order to record demonstrations. + /// + /// See [Imitation Learning - Recording Demonstrations] for more information. + /// + /// [GameObject]: https://docs.unity3d.com/Manual/GameObjects.html + /// [Imitation Learning - Recording Demonstrations]: https://docs.unity3d.com/Packages/com.unity.ml-agents@latest/index.html?subfolder=/manual/Learning-Environment-Design-Agents.html#recording-demonstrations + /// + [RequireComponent(typeof(Agent))] + [AddComponentMenu("ML Agents/Demonstration Recorder", (int)MenuGroup.Default)] + public class DemonstrationRecorder : MonoBehaviour + { + /// + /// Whether or not to record demonstrations. + /// + [FormerlySerializedAs("record")] + [Tooltip("Whether or not to record demonstrations.")] + public bool Record; + + /// + /// Number of steps to record. The editor will stop playing when it reaches this threshold. + /// Set to zero to record indefinitely. + /// + [Tooltip("Number of steps to record. The editor will stop playing when it reaches this threshold. " + + "Set to zero to record indefinitely.")] + public int NumStepsToRecord; + + /// + /// Base demonstration file name. If multiple files are saved, the additional filenames + /// will have a sequence of unique numbers appended. + /// + [FormerlySerializedAs("demonstrationName")] + [Tooltip("Base demonstration file name. If multiple files are saved, the additional " + + "filenames will have a unique number appended.")] + public string DemonstrationName; + + /// + /// Directory to save the demo files. Will default to a "Demonstrations/" folder in the + /// Application data path if not specified. + /// + [FormerlySerializedAs("demonstrationDirectory")] + [Tooltip("Directory to save the demo files. Will default to " + + "{Application.dataPath}/Demonstrations if not specified.")] + public string DemonstrationDirectory; + + DemonstrationWriter m_DemoWriter; + internal const int MaxNameLength = 16; + + const string k_ExtensionType = ".demo"; + const string k_DefaultDirectoryName = "Demonstrations"; + IFileSystem m_FileSystem; + + Agent m_Agent; + + void OnEnable() + { + m_Agent = GetComponent(); + } + + void Update() + { + if (!Record) + { + return; + } + + LazyInitialize(); + + // Quit when num steps to record is reached + if (NumStepsToRecord > 0 && m_DemoWriter.NumSteps >= NumStepsToRecord) + { + Application.Quit(0); +#if UNITY_EDITOR + UnityEditor.EditorApplication.isPlaying = false; +#endif + } + } + + /// + /// Creates demonstration store for use in recording. + /// Has no effect if the demonstration store was already created. + /// + internal DemonstrationWriter LazyInitialize(IFileSystem fileSystem = null) + { + if (m_DemoWriter != null) + { + return m_DemoWriter; + } + + if (m_Agent == null) + { + m_Agent = GetComponent(); + } + + m_FileSystem = fileSystem ?? new FileSystem(); + var behaviorParams = GetComponent(); + if (string.IsNullOrEmpty(DemonstrationName)) + { + DemonstrationName = behaviorParams.BehaviorName; + } + if (string.IsNullOrEmpty(DemonstrationDirectory)) + { + DemonstrationDirectory = Path.Combine(Application.dataPath, k_DefaultDirectoryName); + } + + DemonstrationName = SanitizeName(DemonstrationName, MaxNameLength); + var filePath = MakeDemonstrationFilePath(m_FileSystem, DemonstrationDirectory, DemonstrationName); + var stream = m_FileSystem.File.Create(filePath); + m_DemoWriter = new DemonstrationWriter(stream); + + AddDemonstrationWriterToAgent(m_DemoWriter); + + return m_DemoWriter; + } + + /// + /// Removes all characters except alphanumerics from demonstration name. + /// Shorten name if it is longer than the maxNameLength. + /// + internal static string SanitizeName(string demoName, int maxNameLength) + { + var rgx = new Regex("[^a-zA-Z0-9 -]"); + demoName = rgx.Replace(demoName, ""); + // If the string is too long, it will overflow the metadata. + if (demoName.Length > maxNameLength) + { + demoName = demoName.Substring(0, maxNameLength); + } + return demoName; + } + + /// + /// Gets a unique path for the DemonstrationName in the DemonstrationDirectory. + /// + /// + /// + /// + /// Unique path. + internal static string MakeDemonstrationFilePath( + IFileSystem fileSystem, string demonstrationDirectory, string demonstrationName + ) + { + // Create the directory if it doesn't already exist + if (!fileSystem.Directory.Exists(demonstrationDirectory)) + { + fileSystem.Directory.CreateDirectory(demonstrationDirectory); + } + + var literalName = demonstrationName; + var filePath = Path.Combine(demonstrationDirectory, literalName + k_ExtensionType); + var uniqueNameCounter = 0; + while (fileSystem.File.Exists(filePath)) + { + // TODO should we use a timestamp instead of a counter here? This loops an increasing number of times + // as the number of demos increases. + literalName = demonstrationName + "_" + uniqueNameCounter; + filePath = Path.Combine(demonstrationDirectory, literalName + k_ExtensionType); + uniqueNameCounter++; + } + + return filePath; + } + + /// + /// Close the DemonstrationWriter and remove it from the Agent. + /// Has no effect if the DemonstrationWriter is already closed (or wasn't opened) + /// + public void Close() + { + if (m_DemoWriter != null) + { + RemoveDemonstrationWriterFromAgent(m_DemoWriter); + + m_DemoWriter.Close(); + m_DemoWriter = null; + } + } + + /// + /// Clean up the DemonstrationWriter when shutting down or destroying the Agent. + /// + void OnDestroy() + { + Close(); + } + + /// + /// Add additional DemonstrationWriter to the Agent. It is still up to the user to Close this + /// DemonstrationWriters when recording is done. + /// + /// `DemonstrationWriter` demonstation writer + public void AddDemonstrationWriterToAgent(DemonstrationWriter demoWriter) + { + var behaviorParams = GetComponent(); + demoWriter.Initialize( + DemonstrationName, + behaviorParams.BrainParameters, + behaviorParams.FullyQualifiedBehaviorName + ); + m_Agent.DemonstrationWriters.Add(demoWriter); + } + + /// + /// Remove additional DemonstrationWriter to the Agent. It is still up to the user to Close this + /// DemonstrationWriters when recording is done. + /// + /// `DemonstrationWriter` demonstation writer + public void RemoveDemonstrationWriterFromAgent(DemonstrationWriter demoWriter) + { + m_Agent.DemonstrationWriters.Remove(demoWriter); + } + } +} diff --git a/com.unity.ml-agents/Runtime/Demonstrations/DemonstrationRecorder.cs.meta b/com.unity.ml-agents/Runtime/Demonstrations/DemonstrationRecorder.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..cde4db8f20fb8dfd296392a5215e63494ba4fe71 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Demonstrations/DemonstrationRecorder.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Demonstrations/DemonstrationSummary.cs b/com.unity.ml-agents/Runtime/Demonstrations/DemonstrationSummary.cs new file mode 100644 index 0000000000000000000000000000000000000000..cb32409913179026c4c80aad9426a2a5dfb9ac87 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Demonstrations/DemonstrationSummary.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using UnityEngine; +using Unity.MLAgents.Policies; + +namespace Unity.MLAgents.Demonstrations +{ + /// + /// Summary of a loaded Demonstration file. Only used for display in the Inspector. + /// + [Serializable] + internal class DemonstrationSummary : ScriptableObject + { + public DemonstrationMetaData metaData; + public BrainParameters brainParameters; + public List observationSummaries; + + public void Initialize(BrainParameters brainParams, + DemonstrationMetaData demonstrationMetaData, List obsSummaries) + { + brainParameters = brainParams; + metaData = demonstrationMetaData; + observationSummaries = obsSummaries; + } + } + + + /// + /// Summary of a loaded Observation. Currently only contains the shape of the Observation. + /// + /// This is necessary because serialization doesn't support nested containers or arrays. + [Serializable] + internal struct ObservationSummary + { + public int[] shape; + } +} diff --git a/com.unity.ml-agents/Runtime/Demonstrations/DemonstrationSummary.cs.meta b/com.unity.ml-agents/Runtime/Demonstrations/DemonstrationSummary.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..91e53800d573ae6cf0aa8a46432396d48885886a Binary files /dev/null and b/com.unity.ml-agents/Runtime/Demonstrations/DemonstrationSummary.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Demonstrations/DemonstrationWriter.cs b/com.unity.ml-agents/Runtime/Demonstrations/DemonstrationWriter.cs new file mode 100644 index 0000000000000000000000000000000000000000..952b0a800d45661ac0199501433c89305d3125bb --- /dev/null +++ b/com.unity.ml-agents/Runtime/Demonstrations/DemonstrationWriter.cs @@ -0,0 +1,160 @@ +using System.IO; +using Google.Protobuf; +using System.Collections.Generic; +using Unity.MLAgents.Sensors; +using Unity.MLAgents.Policies; + +namespace Unity.MLAgents.Demonstrations +{ + /// + /// Responsible for writing demonstration data to stream (typically a file stream). + /// + /// + public class DemonstrationWriter + { + /// + /// Number of bytes reserved for the at the start of the demo file. + /// + internal const int MetaDataBytes = 32; + + DemonstrationMetaData m_MetaData; + Stream m_Writer; + float m_CumulativeReward; + ObservationWriter m_ObservationWriter = new ObservationWriter(); + + /// + /// Create a DemonstrationWriter that will write to the specified stream. + /// The stream must support writes and seeking. + /// + /// Target stream + public DemonstrationWriter(Stream stream) + { + m_Writer = stream; + } + + /// + /// Number of steps written so far. + /// + internal int NumSteps + { + get { return m_MetaData.numberSteps; } + } + + /// + /// Writes the initial data to the stream. + /// + /// Base name of the demonstration file(s). + /// The name of the Brain the agent is attached to. + /// The parameters of the Brain the agent is attached to. + internal void Initialize( + string demonstrationName, BrainParameters brainParameters, string brainName) + { + if (m_Writer == null) + { + // Already closed + return; + } + + m_MetaData = new DemonstrationMetaData { demonstrationName = demonstrationName }; + var metaProto = m_MetaData.ToProto(); + metaProto.WriteDelimitedTo(m_Writer); + + WriteBrainParameters(brainName, brainParameters); + } + + /// + /// Writes meta-data. Note that this is called at the *end* of recording, but writes to the + /// beginning of the file. + /// + void WriteMetadata() + { + if (m_Writer == null) + { + // Already closed + return; + } + + var metaProto = m_MetaData.ToProto(); + var metaProtoBytes = metaProto.ToByteArray(); + m_Writer.Write(metaProtoBytes, 0, metaProtoBytes.Length); + m_Writer.Seek(0, 0); + metaProto.WriteDelimitedTo(m_Writer); + } + + /// + /// Writes brain parameters to file. + /// + /// The name of the Brain the agent is attached to. + /// The parameters of the Brain the agent is attached to. + void WriteBrainParameters(string brainName, BrainParameters brainParameters) + { + if (m_Writer == null) + { + // Already closed + return; + } + + // Writes BrainParameters to file. + m_Writer.Seek(MetaDataBytes + 1, 0); + var brainProto = brainParameters.ToProto(brainName, false); + brainProto.WriteDelimitedTo(m_Writer); + } + + /// + /// Write AgentInfo experience to file. + /// + /// for the agent being recorded. + /// List of sensors to record for the agent. + internal void Record(AgentInfo info, List sensors) + { + if (m_Writer == null) + { + // Already closed + return; + } + + // Increment meta-data counters. + m_MetaData.numberSteps++; + m_CumulativeReward += info.reward; + if (info.done) + { + EndEpisode(); + } + + // Generate observations and add AgentInfo to file. + var agentProto = info.ToInfoActionPairProto(); + foreach (var sensor in sensors) + { + agentProto.AgentInfo.Observations.Add(sensor.GetObservationProto(m_ObservationWriter)); + } + + agentProto.WriteDelimitedTo(m_Writer); + } + + /// + /// Performs all clean-up necessary. + /// + public void Close() + { + if (m_Writer == null) + { + // Already closed + return; + } + + EndEpisode(); + m_MetaData.meanReward = m_CumulativeReward / m_MetaData.numberEpisodes; + WriteMetadata(); + m_Writer.Close(); + m_Writer = null; + } + + /// + /// Performs necessary episode-completion steps. + /// + void EndEpisode() + { + m_MetaData.numberEpisodes += 1; + } + } +} diff --git a/com.unity.ml-agents/Runtime/Demonstrations/DemonstrationWriter.cs.meta b/com.unity.ml-agents/Runtime/Demonstrations/DemonstrationWriter.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..f30f1b22c19c516bb783f4c60704746e3b615f42 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Demonstrations/DemonstrationWriter.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/EnvironmentParameters.cs b/com.unity.ml-agents/Runtime/EnvironmentParameters.cs new file mode 100644 index 0000000000000000000000000000000000000000..dbf69415b0d901ade24fc278f04271a75ffe882f --- /dev/null +++ b/com.unity.ml-agents/Runtime/EnvironmentParameters.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using Unity.MLAgents.SideChannels; + +namespace Unity.MLAgents +{ + /// + /// 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. + /// + public sealed class EnvironmentParameters + { + /// + /// The side channel that is used to receive the new parameter values. + /// + readonly EnvironmentParametersChannel m_Channel; + + /// + /// Constructor. + /// + internal EnvironmentParameters() + { + m_Channel = new EnvironmentParametersChannel(); + SideChannelManager.RegisterSideChannel(m_Channel); + } + + /// + /// 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. + /// + /// The parameter key + /// Default value for this parameter. + /// The parameter value for the specified key. + public float GetWithDefault(string key, float defaultValue) + { + return m_Channel.GetWithDefault(key, defaultValue); + } + + /// + /// 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. + /// + /// The parameter key + /// The callback action + public void RegisterCallback(string key, Action action) + { + m_Channel.RegisterCallback(key, action); + } + + /// + /// Returns a list of all the parameter keys that have received values. + /// + /// List of parameter keys. + public IList Keys() + { + return m_Channel.ListParameters(); + } + + internal void Dispose() + { + SideChannelManager.UnregisterSideChannel(m_Channel); + } + } +} diff --git a/com.unity.ml-agents/Runtime/EnvironmentParameters.cs.meta b/com.unity.ml-agents/Runtime/EnvironmentParameters.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..9e7a85f810cc9006fedaf248d2e6e35379fd515f Binary files /dev/null and b/com.unity.ml-agents/Runtime/EnvironmentParameters.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/EpisodeIdCounter.cs b/com.unity.ml-agents/Runtime/EpisodeIdCounter.cs new file mode 100644 index 0000000000000000000000000000000000000000..7b915294487fe21619e9ff4c3f55c5e8df300b5d --- /dev/null +++ b/com.unity.ml-agents/Runtime/EpisodeIdCounter.cs @@ -0,0 +1,20 @@ +using UnityEngine; + +namespace Unity.MLAgents +{ + internal static class EpisodeIdCounter + { + static int s_Counter; + public static int GetEpisodeId() + { + return s_Counter++; + } +#if UNITY_EDITOR + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] + static void ResetStaticsOnLoad() + { + s_Counter = 0; + } +#endif + } +} diff --git a/com.unity.ml-agents/Runtime/EpisodeIdCounter.cs.meta b/com.unity.ml-agents/Runtime/EpisodeIdCounter.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..c377f5004bac0e59a67cd1b0207927b03fa7cc37 Binary files /dev/null and b/com.unity.ml-agents/Runtime/EpisodeIdCounter.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Grpc.meta b/com.unity.ml-agents/Runtime/Grpc.meta new file mode 100644 index 0000000000000000000000000000000000000000..f9d48bfc0fc22501b89a33cf23eb7ea48c8adeeb Binary files /dev/null and b/com.unity.ml-agents/Runtime/Grpc.meta differ diff --git a/com.unity.ml-agents/Runtime/Grpc/AssemblyInfo.cs b/com.unity.ml-agents/Runtime/Grpc/AssemblyInfo.cs new file mode 100644 index 0000000000000000000000000000000000000000..b740e05db8222778e6a9c03814c187469ace18b1 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Grpc/AssemblyInfo.cs @@ -0,0 +1,7 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Unity.ML-Agents")] +[assembly: InternalsVisibleTo("Unity.ML-Agents.Editor")] +[assembly: InternalsVisibleTo("Unity.ML-Agents.Editor.Tests")] +[assembly: InternalsVisibleTo("Unity.ML-Agents.Runtime.Sensor.Tests")] +[assembly: InternalsVisibleTo("Unity.ML-Agents.Runtime.Utils.Tests")] diff --git a/com.unity.ml-agents/Runtime/Grpc/AssemblyInfo.cs.meta b/com.unity.ml-agents/Runtime/Grpc/AssemblyInfo.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..cf7b4f0f10f2c1178136acea667960aaf609ffc9 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Grpc/AssemblyInfo.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects.meta b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects.meta new file mode 100644 index 0000000000000000000000000000000000000000..cef92044c3752acd5a6fd60b72f73a3f96070791 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects.meta differ diff --git a/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/AgentAction.cs b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/AgentAction.cs new file mode 100644 index 0000000000000000000000000000000000000000..3eb0a357a2991dcbeffbb6e94131200dba3a18d5 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/AgentAction.cs @@ -0,0 +1,242 @@ +// +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: mlagents_envs/communicator_objects/agent_action.proto +// +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = global::Google.Protobuf; +using pbc = global::Google.Protobuf.Collections; +using pbr = global::Google.Protobuf.Reflection; +using scg = global::System.Collections.Generic; +namespace Unity.MLAgents.CommunicatorObjects { + + /// Holder for reflection information generated from mlagents_envs/communicator_objects/agent_action.proto + internal static partial class AgentActionReflection { + + #region Descriptor + /// File descriptor for mlagents_envs/communicator_objects/agent_action.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static AgentActionReflection() { + byte[] descriptorData = global::System.Convert.FromBase64String( + string.Concat( + "CjVtbGFnZW50c19lbnZzL2NvbW11bmljYXRvcl9vYmplY3RzL2FnZW50X2Fj", + "dGlvbi5wcm90bxIUY29tbXVuaWNhdG9yX29iamVjdHMijAEKEEFnZW50QWN0", + "aW9uUHJvdG8SIQoZdmVjdG9yX2FjdGlvbnNfZGVwcmVjYXRlZBgBIAMoAhIN", + "CgV2YWx1ZRgEIAEoAhIaChJjb250aW51b3VzX2FjdGlvbnMYBiADKAISGAoQ", + "ZGlzY3JldGVfYWN0aW9ucxgHIAMoBUoECAIQA0oECAMQBEoECAUQBkIlqgIi", + "VW5pdHkuTUxBZ2VudHMuQ29tbXVuaWNhdG9yT2JqZWN0c2IGcHJvdG8z")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { }, + new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(global::Unity.MLAgents.CommunicatorObjects.AgentActionProto), global::Unity.MLAgents.CommunicatorObjects.AgentActionProto.Parser, new[]{ "VectorActionsDeprecated", "Value", "ContinuousActions", "DiscreteActions" }, null, null, null) + })); + } + #endregion + + } + #region Messages + internal sealed partial class AgentActionProto : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new AgentActionProto()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Unity.MLAgents.CommunicatorObjects.AgentActionReflection.Descriptor.MessageTypes[0]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public AgentActionProto() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public AgentActionProto(AgentActionProto other) : this() { + vectorActionsDeprecated_ = other.vectorActionsDeprecated_.Clone(); + value_ = other.value_; + continuousActions_ = other.continuousActions_.Clone(); + discreteActions_ = other.discreteActions_.Clone(); + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public AgentActionProto Clone() { + return new AgentActionProto(this); + } + + /// Field number for the "vector_actions_deprecated" field. + public const int VectorActionsDeprecatedFieldNumber = 1; + private static readonly pb::FieldCodec _repeated_vectorActionsDeprecated_codec + = pb::FieldCodec.ForFloat(10); + private readonly pbc::RepeatedField vectorActionsDeprecated_ = new pbc::RepeatedField(); + /// + /// mark as deprecated in communicator v1.3.0 + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public pbc::RepeatedField VectorActionsDeprecated { + get { return vectorActionsDeprecated_; } + } + + /// Field number for the "value" field. + public const int ValueFieldNumber = 4; + private float value_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public float Value { + get { return value_; } + set { + value_ = value; + } + } + + /// Field number for the "continuous_actions" field. + public const int ContinuousActionsFieldNumber = 6; + private static readonly pb::FieldCodec _repeated_continuousActions_codec + = pb::FieldCodec.ForFloat(50); + private readonly pbc::RepeatedField continuousActions_ = new pbc::RepeatedField(); + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public pbc::RepeatedField ContinuousActions { + get { return continuousActions_; } + } + + /// Field number for the "discrete_actions" field. + public const int DiscreteActionsFieldNumber = 7; + private static readonly pb::FieldCodec _repeated_discreteActions_codec + = pb::FieldCodec.ForInt32(58); + private readonly pbc::RepeatedField discreteActions_ = new pbc::RepeatedField(); + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public pbc::RepeatedField DiscreteActions { + get { return discreteActions_; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as AgentActionProto); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(AgentActionProto other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if(!vectorActionsDeprecated_.Equals(other.vectorActionsDeprecated_)) return false; + if (!pbc::ProtobufEqualityComparers.BitwiseSingleEqualityComparer.Equals(Value, other.Value)) return false; + if(!continuousActions_.Equals(other.continuousActions_)) return false; + if(!discreteActions_.Equals(other.discreteActions_)) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + hash ^= vectorActionsDeprecated_.GetHashCode(); + if (Value != 0F) hash ^= pbc::ProtobufEqualityComparers.BitwiseSingleEqualityComparer.GetHashCode(Value); + hash ^= continuousActions_.GetHashCode(); + hash ^= discreteActions_.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + vectorActionsDeprecated_.WriteTo(output, _repeated_vectorActionsDeprecated_codec); + if (Value != 0F) { + output.WriteRawTag(37); + output.WriteFloat(Value); + } + continuousActions_.WriteTo(output, _repeated_continuousActions_codec); + discreteActions_.WriteTo(output, _repeated_discreteActions_codec); + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + size += vectorActionsDeprecated_.CalculateSize(_repeated_vectorActionsDeprecated_codec); + if (Value != 0F) { + size += 1 + 4; + } + size += continuousActions_.CalculateSize(_repeated_continuousActions_codec); + size += discreteActions_.CalculateSize(_repeated_discreteActions_codec); + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(AgentActionProto other) { + if (other == null) { + return; + } + vectorActionsDeprecated_.Add(other.vectorActionsDeprecated_); + if (other.Value != 0F) { + Value = other.Value; + } + continuousActions_.Add(other.continuousActions_); + discreteActions_.Add(other.discreteActions_); + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: + case 13: { + vectorActionsDeprecated_.AddEntriesFrom(input, _repeated_vectorActionsDeprecated_codec); + break; + } + case 37: { + Value = input.ReadFloat(); + break; + } + case 50: + case 53: { + continuousActions_.AddEntriesFrom(input, _repeated_continuousActions_codec); + break; + } + case 58: + case 56: { + discreteActions_.AddEntriesFrom(input, _repeated_discreteActions_codec); + break; + } + } + } + } + + } + + #endregion + +} + +#endregion Designer generated code diff --git a/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/AgentAction.cs.meta b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/AgentAction.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..fed1ad719565539197886cd684bdd8038843e0b4 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/AgentAction.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/AgentInfo.cs b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/AgentInfo.cs new file mode 100644 index 0000000000000000000000000000000000000000..187f2fdab75cef786d1936cc99dc509c4b8227d7 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/AgentInfo.cs @@ -0,0 +1,361 @@ +// +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: mlagents_envs/communicator_objects/agent_info.proto +// +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = global::Google.Protobuf; +using pbc = global::Google.Protobuf.Collections; +using pbr = global::Google.Protobuf.Reflection; +using scg = global::System.Collections.Generic; +namespace Unity.MLAgents.CommunicatorObjects { + + /// Holder for reflection information generated from mlagents_envs/communicator_objects/agent_info.proto + internal static partial class AgentInfoReflection { + + #region Descriptor + /// File descriptor for mlagents_envs/communicator_objects/agent_info.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static AgentInfoReflection() { + byte[] descriptorData = global::System.Convert.FromBase64String( + string.Concat( + "CjNtbGFnZW50c19lbnZzL2NvbW11bmljYXRvcl9vYmplY3RzL2FnZW50X2lu", + "Zm8ucHJvdG8SFGNvbW11bmljYXRvcl9vYmplY3RzGjRtbGFnZW50c19lbnZz", + "L2NvbW11bmljYXRvcl9vYmplY3RzL29ic2VydmF0aW9uLnByb3RvIvkBCg5B", + "Z2VudEluZm9Qcm90bxIOCgZyZXdhcmQYByABKAISDAoEZG9uZRgIIAEoCBIY", + "ChBtYXhfc3RlcF9yZWFjaGVkGAkgASgIEgoKAmlkGAogASgFEhMKC2FjdGlv", + "bl9tYXNrGAsgAygIEjwKDG9ic2VydmF0aW9ucxgNIAMoCzImLmNvbW11bmlj", + "YXRvcl9vYmplY3RzLk9ic2VydmF0aW9uUHJvdG8SEAoIZ3JvdXBfaWQYDiAB", + "KAUSFAoMZ3JvdXBfcmV3YXJkGA8gASgCSgQIARACSgQIAhADSgQIAxAESgQI", + "BBAFSgQIBRAGSgQIBhAHSgQIDBANQiWqAiJVbml0eS5NTEFnZW50cy5Db21t", + "dW5pY2F0b3JPYmplY3RzYgZwcm90bzM=")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { global::Unity.MLAgents.CommunicatorObjects.ObservationReflection.Descriptor, }, + new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(global::Unity.MLAgents.CommunicatorObjects.AgentInfoProto), global::Unity.MLAgents.CommunicatorObjects.AgentInfoProto.Parser, new[]{ "Reward", "Done", "MaxStepReached", "Id", "ActionMask", "Observations", "GroupId", "GroupReward" }, null, null, null) + })); + } + #endregion + + } + #region Messages + internal sealed partial class AgentInfoProto : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new AgentInfoProto()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Unity.MLAgents.CommunicatorObjects.AgentInfoReflection.Descriptor.MessageTypes[0]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public AgentInfoProto() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public AgentInfoProto(AgentInfoProto other) : this() { + reward_ = other.reward_; + done_ = other.done_; + maxStepReached_ = other.maxStepReached_; + id_ = other.id_; + actionMask_ = other.actionMask_.Clone(); + observations_ = other.observations_.Clone(); + groupId_ = other.groupId_; + groupReward_ = other.groupReward_; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public AgentInfoProto Clone() { + return new AgentInfoProto(this); + } + + /// Field number for the "reward" field. + public const int RewardFieldNumber = 7; + private float reward_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public float Reward { + get { return reward_; } + set { + reward_ = value; + } + } + + /// Field number for the "done" field. + public const int DoneFieldNumber = 8; + private bool done_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Done { + get { return done_; } + set { + done_ = value; + } + } + + /// Field number for the "max_step_reached" field. + public const int MaxStepReachedFieldNumber = 9; + private bool maxStepReached_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool MaxStepReached { + get { return maxStepReached_; } + set { + maxStepReached_ = value; + } + } + + /// Field number for the "id" field. + public const int IdFieldNumber = 10; + private int id_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int Id { + get { return id_; } + set { + id_ = value; + } + } + + /// Field number for the "action_mask" field. + public const int ActionMaskFieldNumber = 11; + private static readonly pb::FieldCodec _repeated_actionMask_codec + = pb::FieldCodec.ForBool(90); + private readonly pbc::RepeatedField actionMask_ = new pbc::RepeatedField(); + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public pbc::RepeatedField ActionMask { + get { return actionMask_; } + } + + /// Field number for the "observations" field. + public const int ObservationsFieldNumber = 13; + private static readonly pb::FieldCodec _repeated_observations_codec + = pb::FieldCodec.ForMessage(106, global::Unity.MLAgents.CommunicatorObjects.ObservationProto.Parser); + private readonly pbc::RepeatedField observations_ = new pbc::RepeatedField(); + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public pbc::RepeatedField Observations { + get { return observations_; } + } + + /// Field number for the "group_id" field. + public const int GroupIdFieldNumber = 14; + private int groupId_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int GroupId { + get { return groupId_; } + set { + groupId_ = value; + } + } + + /// Field number for the "group_reward" field. + public const int GroupRewardFieldNumber = 15; + private float groupReward_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public float GroupReward { + get { return groupReward_; } + set { + groupReward_ = value; + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as AgentInfoProto); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(AgentInfoProto other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!pbc::ProtobufEqualityComparers.BitwiseSingleEqualityComparer.Equals(Reward, other.Reward)) return false; + if (Done != other.Done) return false; + if (MaxStepReached != other.MaxStepReached) return false; + if (Id != other.Id) return false; + if(!actionMask_.Equals(other.actionMask_)) return false; + if(!observations_.Equals(other.observations_)) return false; + if (GroupId != other.GroupId) return false; + if (!pbc::ProtobufEqualityComparers.BitwiseSingleEqualityComparer.Equals(GroupReward, other.GroupReward)) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (Reward != 0F) hash ^= pbc::ProtobufEqualityComparers.BitwiseSingleEqualityComparer.GetHashCode(Reward); + if (Done != false) hash ^= Done.GetHashCode(); + if (MaxStepReached != false) hash ^= MaxStepReached.GetHashCode(); + if (Id != 0) hash ^= Id.GetHashCode(); + hash ^= actionMask_.GetHashCode(); + hash ^= observations_.GetHashCode(); + if (GroupId != 0) hash ^= GroupId.GetHashCode(); + if (GroupReward != 0F) hash ^= pbc::ProtobufEqualityComparers.BitwiseSingleEqualityComparer.GetHashCode(GroupReward); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (Reward != 0F) { + output.WriteRawTag(61); + output.WriteFloat(Reward); + } + if (Done != false) { + output.WriteRawTag(64); + output.WriteBool(Done); + } + if (MaxStepReached != false) { + output.WriteRawTag(72); + output.WriteBool(MaxStepReached); + } + if (Id != 0) { + output.WriteRawTag(80); + output.WriteInt32(Id); + } + actionMask_.WriteTo(output, _repeated_actionMask_codec); + observations_.WriteTo(output, _repeated_observations_codec); + if (GroupId != 0) { + output.WriteRawTag(112); + output.WriteInt32(GroupId); + } + if (GroupReward != 0F) { + output.WriteRawTag(125); + output.WriteFloat(GroupReward); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (Reward != 0F) { + size += 1 + 4; + } + if (Done != false) { + size += 1 + 1; + } + if (MaxStepReached != false) { + size += 1 + 1; + } + if (Id != 0) { + size += 1 + pb::CodedOutputStream.ComputeInt32Size(Id); + } + size += actionMask_.CalculateSize(_repeated_actionMask_codec); + size += observations_.CalculateSize(_repeated_observations_codec); + if (GroupId != 0) { + size += 1 + pb::CodedOutputStream.ComputeInt32Size(GroupId); + } + if (GroupReward != 0F) { + size += 1 + 4; + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(AgentInfoProto other) { + if (other == null) { + return; + } + if (other.Reward != 0F) { + Reward = other.Reward; + } + if (other.Done != false) { + Done = other.Done; + } + if (other.MaxStepReached != false) { + MaxStepReached = other.MaxStepReached; + } + if (other.Id != 0) { + Id = other.Id; + } + actionMask_.Add(other.actionMask_); + observations_.Add(other.observations_); + if (other.GroupId != 0) { + GroupId = other.GroupId; + } + if (other.GroupReward != 0F) { + GroupReward = other.GroupReward; + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 61: { + Reward = input.ReadFloat(); + break; + } + case 64: { + Done = input.ReadBool(); + break; + } + case 72: { + MaxStepReached = input.ReadBool(); + break; + } + case 80: { + Id = input.ReadInt32(); + break; + } + case 90: + case 88: { + actionMask_.AddEntriesFrom(input, _repeated_actionMask_codec); + break; + } + case 106: { + observations_.AddEntriesFrom(input, _repeated_observations_codec); + break; + } + case 112: { + GroupId = input.ReadInt32(); + break; + } + case 125: { + GroupReward = input.ReadFloat(); + break; + } + } + } + } + + } + + #endregion + +} + +#endregion Designer generated code diff --git a/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/AgentInfo.cs.meta b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/AgentInfo.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..bc1ed199d657d0e50da4ddacaa65fa905427fcd0 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/AgentInfo.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/AgentInfoActionPair.cs b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/AgentInfoActionPair.cs new file mode 100644 index 0000000000000000000000000000000000000000..37cd219c7339d8d0f19db4096dc9a5b25f827a1b --- /dev/null +++ b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/AgentInfoActionPair.cs @@ -0,0 +1,219 @@ +// +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: mlagents_envs/communicator_objects/agent_info_action_pair.proto +// +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = global::Google.Protobuf; +using pbc = global::Google.Protobuf.Collections; +using pbr = global::Google.Protobuf.Reflection; +using scg = global::System.Collections.Generic; +namespace Unity.MLAgents.CommunicatorObjects { + + /// Holder for reflection information generated from mlagents_envs/communicator_objects/agent_info_action_pair.proto + internal static partial class AgentInfoActionPairReflection { + + #region Descriptor + /// File descriptor for mlagents_envs/communicator_objects/agent_info_action_pair.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static AgentInfoActionPairReflection() { + byte[] descriptorData = global::System.Convert.FromBase64String( + string.Concat( + "Cj9tbGFnZW50c19lbnZzL2NvbW11bmljYXRvcl9vYmplY3RzL2FnZW50X2lu", + "Zm9fYWN0aW9uX3BhaXIucHJvdG8SFGNvbW11bmljYXRvcl9vYmplY3RzGjNt", + "bGFnZW50c19lbnZzL2NvbW11bmljYXRvcl9vYmplY3RzL2FnZW50X2luZm8u", + "cHJvdG8aNW1sYWdlbnRzX2VudnMvY29tbXVuaWNhdG9yX29iamVjdHMvYWdl", + "bnRfYWN0aW9uLnByb3RvIpEBChhBZ2VudEluZm9BY3Rpb25QYWlyUHJvdG8S", + "OAoKYWdlbnRfaW5mbxgBIAEoCzIkLmNvbW11bmljYXRvcl9vYmplY3RzLkFn", + "ZW50SW5mb1Byb3RvEjsKC2FjdGlvbl9pbmZvGAIgASgLMiYuY29tbXVuaWNh", + "dG9yX29iamVjdHMuQWdlbnRBY3Rpb25Qcm90b0IlqgIiVW5pdHkuTUxBZ2Vu", + "dHMuQ29tbXVuaWNhdG9yT2JqZWN0c2IGcHJvdG8z")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { global::Unity.MLAgents.CommunicatorObjects.AgentInfoReflection.Descriptor, global::Unity.MLAgents.CommunicatorObjects.AgentActionReflection.Descriptor, }, + new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(global::Unity.MLAgents.CommunicatorObjects.AgentInfoActionPairProto), global::Unity.MLAgents.CommunicatorObjects.AgentInfoActionPairProto.Parser, new[]{ "AgentInfo", "ActionInfo" }, null, null, null) + })); + } + #endregion + + } + #region Messages + internal sealed partial class AgentInfoActionPairProto : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new AgentInfoActionPairProto()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Unity.MLAgents.CommunicatorObjects.AgentInfoActionPairReflection.Descriptor.MessageTypes[0]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public AgentInfoActionPairProto() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public AgentInfoActionPairProto(AgentInfoActionPairProto other) : this() { + AgentInfo = other.agentInfo_ != null ? other.AgentInfo.Clone() : null; + ActionInfo = other.actionInfo_ != null ? other.ActionInfo.Clone() : null; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public AgentInfoActionPairProto Clone() { + return new AgentInfoActionPairProto(this); + } + + /// Field number for the "agent_info" field. + public const int AgentInfoFieldNumber = 1; + private global::Unity.MLAgents.CommunicatorObjects.AgentInfoProto agentInfo_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Unity.MLAgents.CommunicatorObjects.AgentInfoProto AgentInfo { + get { return agentInfo_; } + set { + agentInfo_ = value; + } + } + + /// Field number for the "action_info" field. + public const int ActionInfoFieldNumber = 2; + private global::Unity.MLAgents.CommunicatorObjects.AgentActionProto actionInfo_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Unity.MLAgents.CommunicatorObjects.AgentActionProto ActionInfo { + get { return actionInfo_; } + set { + actionInfo_ = value; + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as AgentInfoActionPairProto); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(AgentInfoActionPairProto other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(AgentInfo, other.AgentInfo)) return false; + if (!object.Equals(ActionInfo, other.ActionInfo)) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (agentInfo_ != null) hash ^= AgentInfo.GetHashCode(); + if (actionInfo_ != null) hash ^= ActionInfo.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (agentInfo_ != null) { + output.WriteRawTag(10); + output.WriteMessage(AgentInfo); + } + if (actionInfo_ != null) { + output.WriteRawTag(18); + output.WriteMessage(ActionInfo); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (agentInfo_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(AgentInfo); + } + if (actionInfo_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(ActionInfo); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(AgentInfoActionPairProto other) { + if (other == null) { + return; + } + if (other.agentInfo_ != null) { + if (agentInfo_ == null) { + agentInfo_ = new global::Unity.MLAgents.CommunicatorObjects.AgentInfoProto(); + } + AgentInfo.MergeFrom(other.AgentInfo); + } + if (other.actionInfo_ != null) { + if (actionInfo_ == null) { + actionInfo_ = new global::Unity.MLAgents.CommunicatorObjects.AgentActionProto(); + } + ActionInfo.MergeFrom(other.ActionInfo); + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: { + if (agentInfo_ == null) { + agentInfo_ = new global::Unity.MLAgents.CommunicatorObjects.AgentInfoProto(); + } + input.ReadMessage(agentInfo_); + break; + } + case 18: { + if (actionInfo_ == null) { + actionInfo_ = new global::Unity.MLAgents.CommunicatorObjects.AgentActionProto(); + } + input.ReadMessage(actionInfo_); + break; + } + } + } + } + + } + + #endregion + +} + +#endregion Designer generated code diff --git a/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/AgentInfoActionPair.cs.meta b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/AgentInfoActionPair.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..0ee8c86cef5d0379e437ccdaa462668bf1bd5c1b Binary files /dev/null and b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/AgentInfoActionPair.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/BrainParameters.cs b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/BrainParameters.cs new file mode 100644 index 0000000000000000000000000000000000000000..65b57f4ea3c8ba5637650064330a6ea619434c87 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/BrainParameters.cs @@ -0,0 +1,524 @@ +// +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: mlagents_envs/communicator_objects/brain_parameters.proto +// +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = global::Google.Protobuf; +using pbc = global::Google.Protobuf.Collections; +using pbr = global::Google.Protobuf.Reflection; +using scg = global::System.Collections.Generic; +namespace Unity.MLAgents.CommunicatorObjects { + + /// Holder for reflection information generated from mlagents_envs/communicator_objects/brain_parameters.proto + internal static partial class BrainParametersReflection { + + #region Descriptor + /// File descriptor for mlagents_envs/communicator_objects/brain_parameters.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static BrainParametersReflection() { + byte[] descriptorData = global::System.Convert.FromBase64String( + string.Concat( + "CjltbGFnZW50c19lbnZzL2NvbW11bmljYXRvcl9vYmplY3RzL2JyYWluX3Bh", + "cmFtZXRlcnMucHJvdG8SFGNvbW11bmljYXRvcl9vYmplY3RzGjNtbGFnZW50", + "c19lbnZzL2NvbW11bmljYXRvcl9vYmplY3RzL3NwYWNlX3R5cGUucHJvdG8i", + "iwEKD0FjdGlvblNwZWNQcm90bxIeChZudW1fY29udGludW91c19hY3Rpb25z", + "GAEgASgFEhwKFG51bV9kaXNjcmV0ZV9hY3Rpb25zGAIgASgFEh0KFWRpc2Ny", + "ZXRlX2JyYW5jaF9zaXplcxgDIAMoBRIbChNhY3Rpb25fZGVzY3JpcHRpb25z", + "GAQgAygJIrYCChRCcmFpblBhcmFtZXRlcnNQcm90bxIlCh12ZWN0b3JfYWN0", + "aW9uX3NpemVfZGVwcmVjYXRlZBgDIAMoBRItCiV2ZWN0b3JfYWN0aW9uX2Rl", + "c2NyaXB0aW9uc19kZXByZWNhdGVkGAUgAygJElEKI3ZlY3Rvcl9hY3Rpb25f", + "c3BhY2VfdHlwZV9kZXByZWNhdGVkGAYgASgOMiQuY29tbXVuaWNhdG9yX29i", + "amVjdHMuU3BhY2VUeXBlUHJvdG8SEgoKYnJhaW5fbmFtZRgHIAEoCRITCgtp", + "c190cmFpbmluZxgIIAEoCBI6CgthY3Rpb25fc3BlYxgJIAEoCzIlLmNvbW11", + "bmljYXRvcl9vYmplY3RzLkFjdGlvblNwZWNQcm90b0oECAEQAkoECAIQA0oE", + "CAQQBUIlqgIiVW5pdHkuTUxBZ2VudHMuQ29tbXVuaWNhdG9yT2JqZWN0c2IG", + "cHJvdG8z")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { global::Unity.MLAgents.CommunicatorObjects.SpaceTypeReflection.Descriptor, }, + new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(global::Unity.MLAgents.CommunicatorObjects.ActionSpecProto), global::Unity.MLAgents.CommunicatorObjects.ActionSpecProto.Parser, new[]{ "NumContinuousActions", "NumDiscreteActions", "DiscreteBranchSizes", "ActionDescriptions" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Unity.MLAgents.CommunicatorObjects.BrainParametersProto), global::Unity.MLAgents.CommunicatorObjects.BrainParametersProto.Parser, new[]{ "VectorActionSizeDeprecated", "VectorActionDescriptionsDeprecated", "VectorActionSpaceTypeDeprecated", "BrainName", "IsTraining", "ActionSpec" }, null, null, null) + })); + } + #endregion + + } + #region Messages + internal sealed partial class ActionSpecProto : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ActionSpecProto()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Unity.MLAgents.CommunicatorObjects.BrainParametersReflection.Descriptor.MessageTypes[0]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public ActionSpecProto() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public ActionSpecProto(ActionSpecProto other) : this() { + numContinuousActions_ = other.numContinuousActions_; + numDiscreteActions_ = other.numDiscreteActions_; + discreteBranchSizes_ = other.discreteBranchSizes_.Clone(); + actionDescriptions_ = other.actionDescriptions_.Clone(); + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public ActionSpecProto Clone() { + return new ActionSpecProto(this); + } + + /// Field number for the "num_continuous_actions" field. + public const int NumContinuousActionsFieldNumber = 1; + private int numContinuousActions_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int NumContinuousActions { + get { return numContinuousActions_; } + set { + numContinuousActions_ = value; + } + } + + /// Field number for the "num_discrete_actions" field. + public const int NumDiscreteActionsFieldNumber = 2; + private int numDiscreteActions_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int NumDiscreteActions { + get { return numDiscreteActions_; } + set { + numDiscreteActions_ = value; + } + } + + /// Field number for the "discrete_branch_sizes" field. + public const int DiscreteBranchSizesFieldNumber = 3; + private static readonly pb::FieldCodec _repeated_discreteBranchSizes_codec + = pb::FieldCodec.ForInt32(26); + private readonly pbc::RepeatedField discreteBranchSizes_ = new pbc::RepeatedField(); + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public pbc::RepeatedField DiscreteBranchSizes { + get { return discreteBranchSizes_; } + } + + /// Field number for the "action_descriptions" field. + public const int ActionDescriptionsFieldNumber = 4; + private static readonly pb::FieldCodec _repeated_actionDescriptions_codec + = pb::FieldCodec.ForString(34); + private readonly pbc::RepeatedField actionDescriptions_ = new pbc::RepeatedField(); + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public pbc::RepeatedField ActionDescriptions { + get { return actionDescriptions_; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as ActionSpecProto); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(ActionSpecProto other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (NumContinuousActions != other.NumContinuousActions) return false; + if (NumDiscreteActions != other.NumDiscreteActions) return false; + if(!discreteBranchSizes_.Equals(other.discreteBranchSizes_)) return false; + if(!actionDescriptions_.Equals(other.actionDescriptions_)) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (NumContinuousActions != 0) hash ^= NumContinuousActions.GetHashCode(); + if (NumDiscreteActions != 0) hash ^= NumDiscreteActions.GetHashCode(); + hash ^= discreteBranchSizes_.GetHashCode(); + hash ^= actionDescriptions_.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (NumContinuousActions != 0) { + output.WriteRawTag(8); + output.WriteInt32(NumContinuousActions); + } + if (NumDiscreteActions != 0) { + output.WriteRawTag(16); + output.WriteInt32(NumDiscreteActions); + } + discreteBranchSizes_.WriteTo(output, _repeated_discreteBranchSizes_codec); + actionDescriptions_.WriteTo(output, _repeated_actionDescriptions_codec); + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (NumContinuousActions != 0) { + size += 1 + pb::CodedOutputStream.ComputeInt32Size(NumContinuousActions); + } + if (NumDiscreteActions != 0) { + size += 1 + pb::CodedOutputStream.ComputeInt32Size(NumDiscreteActions); + } + size += discreteBranchSizes_.CalculateSize(_repeated_discreteBranchSizes_codec); + size += actionDescriptions_.CalculateSize(_repeated_actionDescriptions_codec); + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(ActionSpecProto other) { + if (other == null) { + return; + } + if (other.NumContinuousActions != 0) { + NumContinuousActions = other.NumContinuousActions; + } + if (other.NumDiscreteActions != 0) { + NumDiscreteActions = other.NumDiscreteActions; + } + discreteBranchSizes_.Add(other.discreteBranchSizes_); + actionDescriptions_.Add(other.actionDescriptions_); + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 8: { + NumContinuousActions = input.ReadInt32(); + break; + } + case 16: { + NumDiscreteActions = input.ReadInt32(); + break; + } + case 26: + case 24: { + discreteBranchSizes_.AddEntriesFrom(input, _repeated_discreteBranchSizes_codec); + break; + } + case 34: { + actionDescriptions_.AddEntriesFrom(input, _repeated_actionDescriptions_codec); + break; + } + } + } + } + + } + + internal sealed partial class BrainParametersProto : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new BrainParametersProto()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Unity.MLAgents.CommunicatorObjects.BrainParametersReflection.Descriptor.MessageTypes[1]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public BrainParametersProto() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public BrainParametersProto(BrainParametersProto other) : this() { + vectorActionSizeDeprecated_ = other.vectorActionSizeDeprecated_.Clone(); + vectorActionDescriptionsDeprecated_ = other.vectorActionDescriptionsDeprecated_.Clone(); + vectorActionSpaceTypeDeprecated_ = other.vectorActionSpaceTypeDeprecated_; + brainName_ = other.brainName_; + isTraining_ = other.isTraining_; + ActionSpec = other.actionSpec_ != null ? other.ActionSpec.Clone() : null; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public BrainParametersProto Clone() { + return new BrainParametersProto(this); + } + + /// Field number for the "vector_action_size_deprecated" field. + public const int VectorActionSizeDeprecatedFieldNumber = 3; + private static readonly pb::FieldCodec _repeated_vectorActionSizeDeprecated_codec + = pb::FieldCodec.ForInt32(26); + private readonly pbc::RepeatedField vectorActionSizeDeprecated_ = new pbc::RepeatedField(); + /// + /// mark as deprecated in communicator v1.3.0 + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public pbc::RepeatedField VectorActionSizeDeprecated { + get { return vectorActionSizeDeprecated_; } + } + + /// Field number for the "vector_action_descriptions_deprecated" field. + public const int VectorActionDescriptionsDeprecatedFieldNumber = 5; + private static readonly pb::FieldCodec _repeated_vectorActionDescriptionsDeprecated_codec + = pb::FieldCodec.ForString(42); + private readonly pbc::RepeatedField vectorActionDescriptionsDeprecated_ = new pbc::RepeatedField(); + /// + /// mark as deprecated in communicator v1.3.0 + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public pbc::RepeatedField VectorActionDescriptionsDeprecated { + get { return vectorActionDescriptionsDeprecated_; } + } + + /// Field number for the "vector_action_space_type_deprecated" field. + public const int VectorActionSpaceTypeDeprecatedFieldNumber = 6; + private global::Unity.MLAgents.CommunicatorObjects.SpaceTypeProto vectorActionSpaceTypeDeprecated_ = 0; + /// + /// mark as deprecated in communicator v1.3.0 + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Unity.MLAgents.CommunicatorObjects.SpaceTypeProto VectorActionSpaceTypeDeprecated { + get { return vectorActionSpaceTypeDeprecated_; } + set { + vectorActionSpaceTypeDeprecated_ = value; + } + } + + /// Field number for the "brain_name" field. + public const int BrainNameFieldNumber = 7; + private string brainName_ = ""; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public string BrainName { + get { return brainName_; } + set { + brainName_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "is_training" field. + public const int IsTrainingFieldNumber = 8; + private bool isTraining_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool IsTraining { + get { return isTraining_; } + set { + isTraining_ = value; + } + } + + /// Field number for the "action_spec" field. + public const int ActionSpecFieldNumber = 9; + private global::Unity.MLAgents.CommunicatorObjects.ActionSpecProto actionSpec_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Unity.MLAgents.CommunicatorObjects.ActionSpecProto ActionSpec { + get { return actionSpec_; } + set { + actionSpec_ = value; + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as BrainParametersProto); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(BrainParametersProto other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if(!vectorActionSizeDeprecated_.Equals(other.vectorActionSizeDeprecated_)) return false; + if(!vectorActionDescriptionsDeprecated_.Equals(other.vectorActionDescriptionsDeprecated_)) return false; + if (VectorActionSpaceTypeDeprecated != other.VectorActionSpaceTypeDeprecated) return false; + if (BrainName != other.BrainName) return false; + if (IsTraining != other.IsTraining) return false; + if (!object.Equals(ActionSpec, other.ActionSpec)) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + hash ^= vectorActionSizeDeprecated_.GetHashCode(); + hash ^= vectorActionDescriptionsDeprecated_.GetHashCode(); + if (VectorActionSpaceTypeDeprecated != 0) hash ^= VectorActionSpaceTypeDeprecated.GetHashCode(); + if (BrainName.Length != 0) hash ^= BrainName.GetHashCode(); + if (IsTraining != false) hash ^= IsTraining.GetHashCode(); + if (actionSpec_ != null) hash ^= ActionSpec.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + vectorActionSizeDeprecated_.WriteTo(output, _repeated_vectorActionSizeDeprecated_codec); + vectorActionDescriptionsDeprecated_.WriteTo(output, _repeated_vectorActionDescriptionsDeprecated_codec); + if (VectorActionSpaceTypeDeprecated != 0) { + output.WriteRawTag(48); + output.WriteEnum((int) VectorActionSpaceTypeDeprecated); + } + if (BrainName.Length != 0) { + output.WriteRawTag(58); + output.WriteString(BrainName); + } + if (IsTraining != false) { + output.WriteRawTag(64); + output.WriteBool(IsTraining); + } + if (actionSpec_ != null) { + output.WriteRawTag(74); + output.WriteMessage(ActionSpec); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + size += vectorActionSizeDeprecated_.CalculateSize(_repeated_vectorActionSizeDeprecated_codec); + size += vectorActionDescriptionsDeprecated_.CalculateSize(_repeated_vectorActionDescriptionsDeprecated_codec); + if (VectorActionSpaceTypeDeprecated != 0) { + size += 1 + pb::CodedOutputStream.ComputeEnumSize((int) VectorActionSpaceTypeDeprecated); + } + if (BrainName.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(BrainName); + } + if (IsTraining != false) { + size += 1 + 1; + } + if (actionSpec_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(ActionSpec); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(BrainParametersProto other) { + if (other == null) { + return; + } + vectorActionSizeDeprecated_.Add(other.vectorActionSizeDeprecated_); + vectorActionDescriptionsDeprecated_.Add(other.vectorActionDescriptionsDeprecated_); + if (other.VectorActionSpaceTypeDeprecated != 0) { + VectorActionSpaceTypeDeprecated = other.VectorActionSpaceTypeDeprecated; + } + if (other.BrainName.Length != 0) { + BrainName = other.BrainName; + } + if (other.IsTraining != false) { + IsTraining = other.IsTraining; + } + if (other.actionSpec_ != null) { + if (actionSpec_ == null) { + actionSpec_ = new global::Unity.MLAgents.CommunicatorObjects.ActionSpecProto(); + } + ActionSpec.MergeFrom(other.ActionSpec); + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 26: + case 24: { + vectorActionSizeDeprecated_.AddEntriesFrom(input, _repeated_vectorActionSizeDeprecated_codec); + break; + } + case 42: { + vectorActionDescriptionsDeprecated_.AddEntriesFrom(input, _repeated_vectorActionDescriptionsDeprecated_codec); + break; + } + case 48: { + vectorActionSpaceTypeDeprecated_ = (global::Unity.MLAgents.CommunicatorObjects.SpaceTypeProto) input.ReadEnum(); + break; + } + case 58: { + BrainName = input.ReadString(); + break; + } + case 64: { + IsTraining = input.ReadBool(); + break; + } + case 74: { + if (actionSpec_ == null) { + actionSpec_ = new global::Unity.MLAgents.CommunicatorObjects.ActionSpecProto(); + } + input.ReadMessage(actionSpec_); + break; + } + } + } + } + + } + + #endregion + +} + +#endregion Designer generated code diff --git a/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/BrainParameters.cs.meta b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/BrainParameters.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..ebbea500b23b2bb9ceaafb4c03947c9adf65e519 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/BrainParameters.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/Capabilities.cs b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/Capabilities.cs new file mode 100644 index 0000000000000000000000000000000000000000..ac267f4c2f06cdecbbd9e156f6597a81700ad640 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/Capabilities.cs @@ -0,0 +1,373 @@ +// +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: mlagents_envs/communicator_objects/capabilities.proto +// +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = global::Google.Protobuf; +using pbc = global::Google.Protobuf.Collections; +using pbr = global::Google.Protobuf.Reflection; +using scg = global::System.Collections.Generic; +namespace Unity.MLAgents.CommunicatorObjects { + + /// Holder for reflection information generated from mlagents_envs/communicator_objects/capabilities.proto + internal static partial class CapabilitiesReflection { + + #region Descriptor + /// File descriptor for mlagents_envs/communicator_objects/capabilities.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static CapabilitiesReflection() { + byte[] descriptorData = global::System.Convert.FromBase64String( + string.Concat( + "CjVtbGFnZW50c19lbnZzL2NvbW11bmljYXRvcl9vYmplY3RzL2NhcGFiaWxp", + "dGllcy5wcm90bxIUY29tbXVuaWNhdG9yX29iamVjdHMi7AEKGFVuaXR5UkxD", + "YXBhYmlsaXRpZXNQcm90bxIaChJiYXNlUkxDYXBhYmlsaXRpZXMYASABKAgS", + "IwobY29uY2F0ZW5hdGVkUG5nT2JzZXJ2YXRpb25zGAIgASgIEiAKGGNvbXBy", + "ZXNzZWRDaGFubmVsTWFwcGluZxgDIAEoCBIVCg1oeWJyaWRBY3Rpb25zGAQg", + "ASgIEhkKEXRyYWluaW5nQW5hbHl0aWNzGAUgASgIEiEKGXZhcmlhYmxlTGVu", + "Z3RoT2JzZXJ2YXRpb24YBiABKAgSGAoQbXVsdGlBZ2VudEdyb3VwcxgHIAEo", + "CEIlqgIiVW5pdHkuTUxBZ2VudHMuQ29tbXVuaWNhdG9yT2JqZWN0c2IGcHJv", + "dG8z")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { }, + new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(global::Unity.MLAgents.CommunicatorObjects.UnityRLCapabilitiesProto), global::Unity.MLAgents.CommunicatorObjects.UnityRLCapabilitiesProto.Parser, new[]{ "BaseRLCapabilities", "ConcatenatedPngObservations", "CompressedChannelMapping", "HybridActions", "TrainingAnalytics", "VariableLengthObservation", "MultiAgentGroups" }, null, null, null) + })); + } + #endregion + + } + #region Messages + /// + /// + /// A Capabilities message that will communicate both C# and Python + /// what features are available to both. + /// + internal sealed partial class UnityRLCapabilitiesProto : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new UnityRLCapabilitiesProto()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Unity.MLAgents.CommunicatorObjects.CapabilitiesReflection.Descriptor.MessageTypes[0]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public UnityRLCapabilitiesProto() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public UnityRLCapabilitiesProto(UnityRLCapabilitiesProto other) : this() { + baseRLCapabilities_ = other.baseRLCapabilities_; + concatenatedPngObservations_ = other.concatenatedPngObservations_; + compressedChannelMapping_ = other.compressedChannelMapping_; + hybridActions_ = other.hybridActions_; + trainingAnalytics_ = other.trainingAnalytics_; + variableLengthObservation_ = other.variableLengthObservation_; + multiAgentGroups_ = other.multiAgentGroups_; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public UnityRLCapabilitiesProto Clone() { + return new UnityRLCapabilitiesProto(this); + } + + /// Field number for the "baseRLCapabilities" field. + public const int BaseRLCapabilitiesFieldNumber = 1; + private bool baseRLCapabilities_; + /// + /// These are the 1.0 capabilities. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool BaseRLCapabilities { + get { return baseRLCapabilities_; } + set { + baseRLCapabilities_ = value; + } + } + + /// Field number for the "concatenatedPngObservations" field. + public const int ConcatenatedPngObservationsFieldNumber = 2; + private bool concatenatedPngObservations_; + /// + /// concatenated PNG files for compressed visual observations with >3 channels. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool ConcatenatedPngObservations { + get { return concatenatedPngObservations_; } + set { + concatenatedPngObservations_ = value; + } + } + + /// Field number for the "compressedChannelMapping" field. + public const int CompressedChannelMappingFieldNumber = 3; + private bool compressedChannelMapping_; + /// + /// compression mapping for stacking compressed observations. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool CompressedChannelMapping { + get { return compressedChannelMapping_; } + set { + compressedChannelMapping_ = value; + } + } + + /// Field number for the "hybridActions" field. + public const int HybridActionsFieldNumber = 4; + private bool hybridActions_; + /// + /// support for hybrid action spaces (discrete + continuous) + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool HybridActions { + get { return hybridActions_; } + set { + hybridActions_ = value; + } + } + + /// Field number for the "trainingAnalytics" field. + public const int TrainingAnalyticsFieldNumber = 5; + private bool trainingAnalytics_; + /// + /// support for training analytics + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool TrainingAnalytics { + get { return trainingAnalytics_; } + set { + trainingAnalytics_ = value; + } + } + + /// Field number for the "variableLengthObservation" field. + public const int VariableLengthObservationFieldNumber = 6; + private bool variableLengthObservation_; + /// + /// Support for variable length observations of rank 2 + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool VariableLengthObservation { + get { return variableLengthObservation_; } + set { + variableLengthObservation_ = value; + } + } + + /// Field number for the "multiAgentGroups" field. + public const int MultiAgentGroupsFieldNumber = 7; + private bool multiAgentGroups_; + /// + /// Support for multi agent groups and group rewards + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool MultiAgentGroups { + get { return multiAgentGroups_; } + set { + multiAgentGroups_ = value; + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as UnityRLCapabilitiesProto); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(UnityRLCapabilitiesProto other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (BaseRLCapabilities != other.BaseRLCapabilities) return false; + if (ConcatenatedPngObservations != other.ConcatenatedPngObservations) return false; + if (CompressedChannelMapping != other.CompressedChannelMapping) return false; + if (HybridActions != other.HybridActions) return false; + if (TrainingAnalytics != other.TrainingAnalytics) return false; + if (VariableLengthObservation != other.VariableLengthObservation) return false; + if (MultiAgentGroups != other.MultiAgentGroups) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (BaseRLCapabilities != false) hash ^= BaseRLCapabilities.GetHashCode(); + if (ConcatenatedPngObservations != false) hash ^= ConcatenatedPngObservations.GetHashCode(); + if (CompressedChannelMapping != false) hash ^= CompressedChannelMapping.GetHashCode(); + if (HybridActions != false) hash ^= HybridActions.GetHashCode(); + if (TrainingAnalytics != false) hash ^= TrainingAnalytics.GetHashCode(); + if (VariableLengthObservation != false) hash ^= VariableLengthObservation.GetHashCode(); + if (MultiAgentGroups != false) hash ^= MultiAgentGroups.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (BaseRLCapabilities != false) { + output.WriteRawTag(8); + output.WriteBool(BaseRLCapabilities); + } + if (ConcatenatedPngObservations != false) { + output.WriteRawTag(16); + output.WriteBool(ConcatenatedPngObservations); + } + if (CompressedChannelMapping != false) { + output.WriteRawTag(24); + output.WriteBool(CompressedChannelMapping); + } + if (HybridActions != false) { + output.WriteRawTag(32); + output.WriteBool(HybridActions); + } + if (TrainingAnalytics != false) { + output.WriteRawTag(40); + output.WriteBool(TrainingAnalytics); + } + if (VariableLengthObservation != false) { + output.WriteRawTag(48); + output.WriteBool(VariableLengthObservation); + } + if (MultiAgentGroups != false) { + output.WriteRawTag(56); + output.WriteBool(MultiAgentGroups); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (BaseRLCapabilities != false) { + size += 1 + 1; + } + if (ConcatenatedPngObservations != false) { + size += 1 + 1; + } + if (CompressedChannelMapping != false) { + size += 1 + 1; + } + if (HybridActions != false) { + size += 1 + 1; + } + if (TrainingAnalytics != false) { + size += 1 + 1; + } + if (VariableLengthObservation != false) { + size += 1 + 1; + } + if (MultiAgentGroups != false) { + size += 1 + 1; + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(UnityRLCapabilitiesProto other) { + if (other == null) { + return; + } + if (other.BaseRLCapabilities != false) { + BaseRLCapabilities = other.BaseRLCapabilities; + } + if (other.ConcatenatedPngObservations != false) { + ConcatenatedPngObservations = other.ConcatenatedPngObservations; + } + if (other.CompressedChannelMapping != false) { + CompressedChannelMapping = other.CompressedChannelMapping; + } + if (other.HybridActions != false) { + HybridActions = other.HybridActions; + } + if (other.TrainingAnalytics != false) { + TrainingAnalytics = other.TrainingAnalytics; + } + if (other.VariableLengthObservation != false) { + VariableLengthObservation = other.VariableLengthObservation; + } + if (other.MultiAgentGroups != false) { + MultiAgentGroups = other.MultiAgentGroups; + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 8: { + BaseRLCapabilities = input.ReadBool(); + break; + } + case 16: { + ConcatenatedPngObservations = input.ReadBool(); + break; + } + case 24: { + CompressedChannelMapping = input.ReadBool(); + break; + } + case 32: { + HybridActions = input.ReadBool(); + break; + } + case 40: { + TrainingAnalytics = input.ReadBool(); + break; + } + case 48: { + VariableLengthObservation = input.ReadBool(); + break; + } + case 56: { + MultiAgentGroups = input.ReadBool(); + break; + } + } + } + } + + } + + #endregion + +} + +#endregion Designer generated code diff --git a/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/Capabilities.cs.meta b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/Capabilities.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..11112b8196a582e4188520ebe95ced22f5ff0fbb Binary files /dev/null and b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/Capabilities.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/Command.cs b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/Command.cs new file mode 100644 index 0000000000000000000000000000000000000000..1220f9f9eec87040d387566cc1695005aee6f29e --- /dev/null +++ b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/Command.cs @@ -0,0 +1,49 @@ +// +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: mlagents_envs/communicator_objects/command.proto +// +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = global::Google.Protobuf; +using pbc = global::Google.Protobuf.Collections; +using pbr = global::Google.Protobuf.Reflection; +using scg = global::System.Collections.Generic; +namespace Unity.MLAgents.CommunicatorObjects { + + /// Holder for reflection information generated from mlagents_envs/communicator_objects/command.proto + internal static partial class CommandReflection { + + #region Descriptor + /// File descriptor for mlagents_envs/communicator_objects/command.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static CommandReflection() { + byte[] descriptorData = global::System.Convert.FromBase64String( + string.Concat( + "CjBtbGFnZW50c19lbnZzL2NvbW11bmljYXRvcl9vYmplY3RzL2NvbW1hbmQu", + "cHJvdG8SFGNvbW11bmljYXRvcl9vYmplY3RzKi0KDENvbW1hbmRQcm90bxII", + "CgRTVEVQEAASCQoFUkVTRVQQARIICgRRVUlUEAJCJaoCIlVuaXR5Lk1MQWdl", + "bnRzLkNvbW11bmljYXRvck9iamVjdHNiBnByb3RvMw==")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { }, + new pbr::GeneratedClrTypeInfo(new[] {typeof(global::Unity.MLAgents.CommunicatorObjects.CommandProto), }, null)); + } + #endregion + + } + #region Enums + internal enum CommandProto { + [pbr::OriginalName("STEP")] Step = 0, + [pbr::OriginalName("RESET")] Reset = 1, + [pbr::OriginalName("QUIT")] Quit = 2, + } + + #endregion + +} + +#endregion Designer generated code diff --git a/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/Command.cs.meta b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/Command.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..ee4971371dcf93dd6ddff2fee454c47dac7b213d Binary files /dev/null and b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/Command.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/CustomResetParameters.cs b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/CustomResetParameters.cs new file mode 100644 index 0000000000000000000000000000000000000000..45099b04c7f8545c5bc4ebc39f6b6294025a999d --- /dev/null +++ b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/CustomResetParameters.cs @@ -0,0 +1,146 @@ +// +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: mlagents_envs/communicator_objects/custom_reset_parameters.proto +// +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = global::Google.Protobuf; +using pbc = global::Google.Protobuf.Collections; +using pbr = global::Google.Protobuf.Reflection; +using scg = global::System.Collections.Generic; +namespace Unity.MLAgents.CommunicatorObjects { + + /// Holder for reflection information generated from mlagents_envs/communicator_objects/custom_reset_parameters.proto + internal static partial class CustomResetParametersReflection { + + #region Descriptor + /// File descriptor for mlagents_envs/communicator_objects/custom_reset_parameters.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static CustomResetParametersReflection() { + byte[] descriptorData = global::System.Convert.FromBase64String( + string.Concat( + "CkBtbGFnZW50c19lbnZzL2NvbW11bmljYXRvcl9vYmplY3RzL2N1c3RvbV9y", + "ZXNldF9wYXJhbWV0ZXJzLnByb3RvEhRjb21tdW5pY2F0b3Jfb2JqZWN0cyIc", + "ChpDdXN0b21SZXNldFBhcmFtZXRlcnNQcm90b0IlqgIiVW5pdHkuTUxBZ2Vu", + "dHMuQ29tbXVuaWNhdG9yT2JqZWN0c2IGcHJvdG8z")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { }, + new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(global::Unity.MLAgents.CommunicatorObjects.CustomResetParametersProto), global::Unity.MLAgents.CommunicatorObjects.CustomResetParametersProto.Parser, null, null, null, null) + })); + } + #endregion + + } + #region Messages + internal sealed partial class CustomResetParametersProto : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new CustomResetParametersProto()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Unity.MLAgents.CommunicatorObjects.CustomResetParametersReflection.Descriptor.MessageTypes[0]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public CustomResetParametersProto() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public CustomResetParametersProto(CustomResetParametersProto other) : this() { + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public CustomResetParametersProto Clone() { + return new CustomResetParametersProto(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as CustomResetParametersProto); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(CustomResetParametersProto other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(CustomResetParametersProto other) { + if (other == null) { + return; + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + } + } + } + + } + + #endregion + +} + +#endregion Designer generated code diff --git a/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/CustomResetParameters.cs.meta b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/CustomResetParameters.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..fb6d1ca9b0aef9d99a13c7536106281ee18fe307 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/CustomResetParameters.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/DemonstrationMeta.cs b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/DemonstrationMeta.cs new file mode 100644 index 0000000000000000000000000000000000000000..58f8ad80225946c0319a8582e5f90394485a7e62 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/DemonstrationMeta.cs @@ -0,0 +1,289 @@ +// +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: mlagents_envs/communicator_objects/demonstration_meta.proto +// +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = global::Google.Protobuf; +using pbc = global::Google.Protobuf.Collections; +using pbr = global::Google.Protobuf.Reflection; +using scg = global::System.Collections.Generic; +namespace Unity.MLAgents.CommunicatorObjects { + + /// Holder for reflection information generated from mlagents_envs/communicator_objects/demonstration_meta.proto + internal static partial class DemonstrationMetaReflection { + + #region Descriptor + /// File descriptor for mlagents_envs/communicator_objects/demonstration_meta.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static DemonstrationMetaReflection() { + byte[] descriptorData = global::System.Convert.FromBase64String( + string.Concat( + "CjttbGFnZW50c19lbnZzL2NvbW11bmljYXRvcl9vYmplY3RzL2RlbW9uc3Ry", + "YXRpb25fbWV0YS5wcm90bxIUY29tbXVuaWNhdG9yX29iamVjdHMijQEKFkRl", + "bW9uc3RyYXRpb25NZXRhUHJvdG8SEwoLYXBpX3ZlcnNpb24YASABKAUSGgoS", + "ZGVtb25zdHJhdGlvbl9uYW1lGAIgASgJEhQKDG51bWJlcl9zdGVwcxgDIAEo", + "BRIXCg9udW1iZXJfZXBpc29kZXMYBCABKAUSEwoLbWVhbl9yZXdhcmQYBSAB", + "KAJCJaoCIlVuaXR5Lk1MQWdlbnRzLkNvbW11bmljYXRvck9iamVjdHNiBnBy", + "b3RvMw==")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { }, + new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(global::Unity.MLAgents.CommunicatorObjects.DemonstrationMetaProto), global::Unity.MLAgents.CommunicatorObjects.DemonstrationMetaProto.Parser, new[]{ "ApiVersion", "DemonstrationName", "NumberSteps", "NumberEpisodes", "MeanReward" }, null, null, null) + })); + } + #endregion + + } + #region Messages + internal sealed partial class DemonstrationMetaProto : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new DemonstrationMetaProto()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Unity.MLAgents.CommunicatorObjects.DemonstrationMetaReflection.Descriptor.MessageTypes[0]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public DemonstrationMetaProto() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public DemonstrationMetaProto(DemonstrationMetaProto other) : this() { + apiVersion_ = other.apiVersion_; + demonstrationName_ = other.demonstrationName_; + numberSteps_ = other.numberSteps_; + numberEpisodes_ = other.numberEpisodes_; + meanReward_ = other.meanReward_; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public DemonstrationMetaProto Clone() { + return new DemonstrationMetaProto(this); + } + + /// Field number for the "api_version" field. + public const int ApiVersionFieldNumber = 1; + private int apiVersion_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int ApiVersion { + get { return apiVersion_; } + set { + apiVersion_ = value; + } + } + + /// Field number for the "demonstration_name" field. + public const int DemonstrationNameFieldNumber = 2; + private string demonstrationName_ = ""; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public string DemonstrationName { + get { return demonstrationName_; } + set { + demonstrationName_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "number_steps" field. + public const int NumberStepsFieldNumber = 3; + private int numberSteps_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int NumberSteps { + get { return numberSteps_; } + set { + numberSteps_ = value; + } + } + + /// Field number for the "number_episodes" field. + public const int NumberEpisodesFieldNumber = 4; + private int numberEpisodes_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int NumberEpisodes { + get { return numberEpisodes_; } + set { + numberEpisodes_ = value; + } + } + + /// Field number for the "mean_reward" field. + public const int MeanRewardFieldNumber = 5; + private float meanReward_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public float MeanReward { + get { return meanReward_; } + set { + meanReward_ = value; + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as DemonstrationMetaProto); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(DemonstrationMetaProto other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (ApiVersion != other.ApiVersion) return false; + if (DemonstrationName != other.DemonstrationName) return false; + if (NumberSteps != other.NumberSteps) return false; + if (NumberEpisodes != other.NumberEpisodes) return false; + if (!pbc::ProtobufEqualityComparers.BitwiseSingleEqualityComparer.Equals(MeanReward, other.MeanReward)) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (ApiVersion != 0) hash ^= ApiVersion.GetHashCode(); + if (DemonstrationName.Length != 0) hash ^= DemonstrationName.GetHashCode(); + if (NumberSteps != 0) hash ^= NumberSteps.GetHashCode(); + if (NumberEpisodes != 0) hash ^= NumberEpisodes.GetHashCode(); + if (MeanReward != 0F) hash ^= pbc::ProtobufEqualityComparers.BitwiseSingleEqualityComparer.GetHashCode(MeanReward); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (ApiVersion != 0) { + output.WriteRawTag(8); + output.WriteInt32(ApiVersion); + } + if (DemonstrationName.Length != 0) { + output.WriteRawTag(18); + output.WriteString(DemonstrationName); + } + if (NumberSteps != 0) { + output.WriteRawTag(24); + output.WriteInt32(NumberSteps); + } + if (NumberEpisodes != 0) { + output.WriteRawTag(32); + output.WriteInt32(NumberEpisodes); + } + if (MeanReward != 0F) { + output.WriteRawTag(45); + output.WriteFloat(MeanReward); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (ApiVersion != 0) { + size += 1 + pb::CodedOutputStream.ComputeInt32Size(ApiVersion); + } + if (DemonstrationName.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(DemonstrationName); + } + if (NumberSteps != 0) { + size += 1 + pb::CodedOutputStream.ComputeInt32Size(NumberSteps); + } + if (NumberEpisodes != 0) { + size += 1 + pb::CodedOutputStream.ComputeInt32Size(NumberEpisodes); + } + if (MeanReward != 0F) { + size += 1 + 4; + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(DemonstrationMetaProto other) { + if (other == null) { + return; + } + if (other.ApiVersion != 0) { + ApiVersion = other.ApiVersion; + } + if (other.DemonstrationName.Length != 0) { + DemonstrationName = other.DemonstrationName; + } + if (other.NumberSteps != 0) { + NumberSteps = other.NumberSteps; + } + if (other.NumberEpisodes != 0) { + NumberEpisodes = other.NumberEpisodes; + } + if (other.MeanReward != 0F) { + MeanReward = other.MeanReward; + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 8: { + ApiVersion = input.ReadInt32(); + break; + } + case 18: { + DemonstrationName = input.ReadString(); + break; + } + case 24: { + NumberSteps = input.ReadInt32(); + break; + } + case 32: { + NumberEpisodes = input.ReadInt32(); + break; + } + case 45: { + MeanReward = input.ReadFloat(); + break; + } + } + } + } + + } + + #endregion + +} + +#endregion Designer generated code diff --git a/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/DemonstrationMeta.cs.meta b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/DemonstrationMeta.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..09e069782b42659ce114c3b45f27b97c0f3d7839 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/DemonstrationMeta.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/EngineConfiguration.cs b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/EngineConfiguration.cs new file mode 100644 index 0000000000000000000000000000000000000000..6a05c09f28b4058d14481d5baaf5a92cc758e6ea --- /dev/null +++ b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/EngineConfiguration.cs @@ -0,0 +1,317 @@ +// +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: mlagents_envs/communicator_objects/engine_configuration.proto +// +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = global::Google.Protobuf; +using pbc = global::Google.Protobuf.Collections; +using pbr = global::Google.Protobuf.Reflection; +using scg = global::System.Collections.Generic; +namespace Unity.MLAgents.CommunicatorObjects { + + /// Holder for reflection information generated from mlagents_envs/communicator_objects/engine_configuration.proto + internal static partial class EngineConfigurationReflection { + + #region Descriptor + /// File descriptor for mlagents_envs/communicator_objects/engine_configuration.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static EngineConfigurationReflection() { + byte[] descriptorData = global::System.Convert.FromBase64String( + string.Concat( + "Cj1tbGFnZW50c19lbnZzL2NvbW11bmljYXRvcl9vYmplY3RzL2VuZ2luZV9j", + "b25maWd1cmF0aW9uLnByb3RvEhRjb21tdW5pY2F0b3Jfb2JqZWN0cyKVAQoY", + "RW5naW5lQ29uZmlndXJhdGlvblByb3RvEg0KBXdpZHRoGAEgASgFEg4KBmhl", + "aWdodBgCIAEoBRIVCg1xdWFsaXR5X2xldmVsGAMgASgFEhIKCnRpbWVfc2Nh", + "bGUYBCABKAISGQoRdGFyZ2V0X2ZyYW1lX3JhdGUYBSABKAUSFAoMc2hvd19t", + "b25pdG9yGAYgASgIQiWqAiJVbml0eS5NTEFnZW50cy5Db21tdW5pY2F0b3JP", + "YmplY3RzYgZwcm90bzM=")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { }, + new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(global::Unity.MLAgents.CommunicatorObjects.EngineConfigurationProto), global::Unity.MLAgents.CommunicatorObjects.EngineConfigurationProto.Parser, new[]{ "Width", "Height", "QualityLevel", "TimeScale", "TargetFrameRate", "ShowMonitor" }, null, null, null) + })); + } + #endregion + + } + #region Messages + internal sealed partial class EngineConfigurationProto : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new EngineConfigurationProto()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Unity.MLAgents.CommunicatorObjects.EngineConfigurationReflection.Descriptor.MessageTypes[0]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public EngineConfigurationProto() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public EngineConfigurationProto(EngineConfigurationProto other) : this() { + width_ = other.width_; + height_ = other.height_; + qualityLevel_ = other.qualityLevel_; + timeScale_ = other.timeScale_; + targetFrameRate_ = other.targetFrameRate_; + showMonitor_ = other.showMonitor_; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public EngineConfigurationProto Clone() { + return new EngineConfigurationProto(this); + } + + /// Field number for the "width" field. + public const int WidthFieldNumber = 1; + private int width_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int Width { + get { return width_; } + set { + width_ = value; + } + } + + /// Field number for the "height" field. + public const int HeightFieldNumber = 2; + private int height_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int Height { + get { return height_; } + set { + height_ = value; + } + } + + /// Field number for the "quality_level" field. + public const int QualityLevelFieldNumber = 3; + private int qualityLevel_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int QualityLevel { + get { return qualityLevel_; } + set { + qualityLevel_ = value; + } + } + + /// Field number for the "time_scale" field. + public const int TimeScaleFieldNumber = 4; + private float timeScale_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public float TimeScale { + get { return timeScale_; } + set { + timeScale_ = value; + } + } + + /// Field number for the "target_frame_rate" field. + public const int TargetFrameRateFieldNumber = 5; + private int targetFrameRate_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int TargetFrameRate { + get { return targetFrameRate_; } + set { + targetFrameRate_ = value; + } + } + + /// Field number for the "show_monitor" field. + public const int ShowMonitorFieldNumber = 6; + private bool showMonitor_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool ShowMonitor { + get { return showMonitor_; } + set { + showMonitor_ = value; + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as EngineConfigurationProto); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(EngineConfigurationProto other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (Width != other.Width) return false; + if (Height != other.Height) return false; + if (QualityLevel != other.QualityLevel) return false; + if (!pbc::ProtobufEqualityComparers.BitwiseSingleEqualityComparer.Equals(TimeScale, other.TimeScale)) return false; + if (TargetFrameRate != other.TargetFrameRate) return false; + if (ShowMonitor != other.ShowMonitor) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (Width != 0) hash ^= Width.GetHashCode(); + if (Height != 0) hash ^= Height.GetHashCode(); + if (QualityLevel != 0) hash ^= QualityLevel.GetHashCode(); + if (TimeScale != 0F) hash ^= pbc::ProtobufEqualityComparers.BitwiseSingleEqualityComparer.GetHashCode(TimeScale); + if (TargetFrameRate != 0) hash ^= TargetFrameRate.GetHashCode(); + if (ShowMonitor != false) hash ^= ShowMonitor.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (Width != 0) { + output.WriteRawTag(8); + output.WriteInt32(Width); + } + if (Height != 0) { + output.WriteRawTag(16); + output.WriteInt32(Height); + } + if (QualityLevel != 0) { + output.WriteRawTag(24); + output.WriteInt32(QualityLevel); + } + if (TimeScale != 0F) { + output.WriteRawTag(37); + output.WriteFloat(TimeScale); + } + if (TargetFrameRate != 0) { + output.WriteRawTag(40); + output.WriteInt32(TargetFrameRate); + } + if (ShowMonitor != false) { + output.WriteRawTag(48); + output.WriteBool(ShowMonitor); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (Width != 0) { + size += 1 + pb::CodedOutputStream.ComputeInt32Size(Width); + } + if (Height != 0) { + size += 1 + pb::CodedOutputStream.ComputeInt32Size(Height); + } + if (QualityLevel != 0) { + size += 1 + pb::CodedOutputStream.ComputeInt32Size(QualityLevel); + } + if (TimeScale != 0F) { + size += 1 + 4; + } + if (TargetFrameRate != 0) { + size += 1 + pb::CodedOutputStream.ComputeInt32Size(TargetFrameRate); + } + if (ShowMonitor != false) { + size += 1 + 1; + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(EngineConfigurationProto other) { + if (other == null) { + return; + } + if (other.Width != 0) { + Width = other.Width; + } + if (other.Height != 0) { + Height = other.Height; + } + if (other.QualityLevel != 0) { + QualityLevel = other.QualityLevel; + } + if (other.TimeScale != 0F) { + TimeScale = other.TimeScale; + } + if (other.TargetFrameRate != 0) { + TargetFrameRate = other.TargetFrameRate; + } + if (other.ShowMonitor != false) { + ShowMonitor = other.ShowMonitor; + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 8: { + Width = input.ReadInt32(); + break; + } + case 16: { + Height = input.ReadInt32(); + break; + } + case 24: { + QualityLevel = input.ReadInt32(); + break; + } + case 37: { + TimeScale = input.ReadFloat(); + break; + } + case 40: { + TargetFrameRate = input.ReadInt32(); + break; + } + case 48: { + ShowMonitor = input.ReadBool(); + break; + } + } + } + } + + } + + #endregion + +} + +#endregion Designer generated code diff --git a/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/EngineConfiguration.cs.meta b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/EngineConfiguration.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..b6a57404447d2307c044685cbb793d9a173501de Binary files /dev/null and b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/EngineConfiguration.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/Header.cs b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/Header.cs new file mode 100644 index 0000000000000000000000000000000000000000..2f38cc8f444972a308d55edf59f3c1879bc61313 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/Header.cs @@ -0,0 +1,202 @@ +// +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: mlagents_envs/communicator_objects/header.proto +// +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = global::Google.Protobuf; +using pbc = global::Google.Protobuf.Collections; +using pbr = global::Google.Protobuf.Reflection; +using scg = global::System.Collections.Generic; +namespace Unity.MLAgents.CommunicatorObjects { + + /// Holder for reflection information generated from mlagents_envs/communicator_objects/header.proto + internal static partial class HeaderReflection { + + #region Descriptor + /// File descriptor for mlagents_envs/communicator_objects/header.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static HeaderReflection() { + byte[] descriptorData = global::System.Convert.FromBase64String( + string.Concat( + "Ci9tbGFnZW50c19lbnZzL2NvbW11bmljYXRvcl9vYmplY3RzL2hlYWRlci5w", + "cm90bxIUY29tbXVuaWNhdG9yX29iamVjdHMiLgoLSGVhZGVyUHJvdG8SDgoG", + "c3RhdHVzGAEgASgFEg8KB21lc3NhZ2UYAiABKAlCJaoCIlVuaXR5Lk1MQWdl", + "bnRzLkNvbW11bmljYXRvck9iamVjdHNiBnByb3RvMw==")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { }, + new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(global::Unity.MLAgents.CommunicatorObjects.HeaderProto), global::Unity.MLAgents.CommunicatorObjects.HeaderProto.Parser, new[]{ "Status", "Message" }, null, null, null) + })); + } + #endregion + + } + #region Messages + internal sealed partial class HeaderProto : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new HeaderProto()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Unity.MLAgents.CommunicatorObjects.HeaderReflection.Descriptor.MessageTypes[0]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public HeaderProto() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public HeaderProto(HeaderProto other) : this() { + status_ = other.status_; + message_ = other.message_; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public HeaderProto Clone() { + return new HeaderProto(this); + } + + /// Field number for the "status" field. + public const int StatusFieldNumber = 1; + private int status_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int Status { + get { return status_; } + set { + status_ = value; + } + } + + /// Field number for the "message" field. + public const int MessageFieldNumber = 2; + private string message_ = ""; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public string Message { + get { return message_; } + set { + message_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as HeaderProto); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(HeaderProto other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (Status != other.Status) return false; + if (Message != other.Message) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (Status != 0) hash ^= Status.GetHashCode(); + if (Message.Length != 0) hash ^= Message.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (Status != 0) { + output.WriteRawTag(8); + output.WriteInt32(Status); + } + if (Message.Length != 0) { + output.WriteRawTag(18); + output.WriteString(Message); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (Status != 0) { + size += 1 + pb::CodedOutputStream.ComputeInt32Size(Status); + } + if (Message.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Message); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(HeaderProto other) { + if (other == null) { + return; + } + if (other.Status != 0) { + Status = other.Status; + } + if (other.Message.Length != 0) { + Message = other.Message; + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 8: { + Status = input.ReadInt32(); + break; + } + case 18: { + Message = input.ReadString(); + break; + } + } + } + } + + } + + #endregion + +} + +#endregion Designer generated code diff --git a/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/Header.cs.meta b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/Header.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..8906e7788c1a67e3ab133728aefa4bd437dc5f56 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/Header.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/Observation.cs b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/Observation.cs new file mode 100644 index 0000000000000000000000000000000000000000..3e23c8d991b742e2a67b9655e9c60899a898088c --- /dev/null +++ b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/Observation.cs @@ -0,0 +1,546 @@ +// +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: mlagents_envs/communicator_objects/observation.proto +// +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = global::Google.Protobuf; +using pbc = global::Google.Protobuf.Collections; +using pbr = global::Google.Protobuf.Reflection; +using scg = global::System.Collections.Generic; +namespace Unity.MLAgents.CommunicatorObjects { + + /// Holder for reflection information generated from mlagents_envs/communicator_objects/observation.proto + internal static partial class ObservationReflection { + + #region Descriptor + /// File descriptor for mlagents_envs/communicator_objects/observation.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static ObservationReflection() { + byte[] descriptorData = global::System.Convert.FromBase64String( + string.Concat( + "CjRtbGFnZW50c19lbnZzL2NvbW11bmljYXRvcl9vYmplY3RzL29ic2VydmF0", + "aW9uLnByb3RvEhRjb21tdW5pY2F0b3Jfb2JqZWN0cyKPAwoQT2JzZXJ2YXRp", + "b25Qcm90bxINCgVzaGFwZRgBIAMoBRJEChBjb21wcmVzc2lvbl90eXBlGAIg", + "ASgOMiouY29tbXVuaWNhdG9yX29iamVjdHMuQ29tcHJlc3Npb25UeXBlUHJv", + "dG8SGQoPY29tcHJlc3NlZF9kYXRhGAMgASgMSAASRgoKZmxvYXRfZGF0YRgE", + "IAEoCzIwLmNvbW11bmljYXRvcl9vYmplY3RzLk9ic2VydmF0aW9uUHJvdG8u", + "RmxvYXREYXRhSAASIgoaY29tcHJlc3NlZF9jaGFubmVsX21hcHBpbmcYBSAD", + "KAUSHAoUZGltZW5zaW9uX3Byb3BlcnRpZXMYBiADKAUSRAoQb2JzZXJ2YXRp", + "b25fdHlwZRgHIAEoDjIqLmNvbW11bmljYXRvcl9vYmplY3RzLk9ic2VydmF0", + "aW9uVHlwZVByb3RvEgwKBG5hbWUYCCABKAkaGQoJRmxvYXREYXRhEgwKBGRh", + "dGEYASADKAJCEgoQb2JzZXJ2YXRpb25fZGF0YSopChRDb21wcmVzc2lvblR5", + "cGVQcm90bxIICgROT05FEAASBwoDUE5HEAEqQAoUT2JzZXJ2YXRpb25UeXBl", + "UHJvdG8SCwoHREVGQVVMVBAAEg8KC0dPQUxfU0lHTkFMEAEiBAgCEAIiBAgD", + "EANCJaoCIlVuaXR5Lk1MQWdlbnRzLkNvbW11bmljYXRvck9iamVjdHNiBnBy", + "b3RvMw==")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { }, + new pbr::GeneratedClrTypeInfo(new[] {typeof(global::Unity.MLAgents.CommunicatorObjects.CompressionTypeProto), typeof(global::Unity.MLAgents.CommunicatorObjects.ObservationTypeProto), }, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(global::Unity.MLAgents.CommunicatorObjects.ObservationProto), global::Unity.MLAgents.CommunicatorObjects.ObservationProto.Parser, new[]{ "Shape", "CompressionType", "CompressedData", "FloatData", "CompressedChannelMapping", "DimensionProperties", "ObservationType", "Name" }, new[]{ "ObservationData" }, null, new pbr::GeneratedClrTypeInfo[] { new pbr::GeneratedClrTypeInfo(typeof(global::Unity.MLAgents.CommunicatorObjects.ObservationProto.Types.FloatData), global::Unity.MLAgents.CommunicatorObjects.ObservationProto.Types.FloatData.Parser, new[]{ "Data" }, null, null, null)}) + })); + } + #endregion + + } + #region Enums + internal enum CompressionTypeProto { + [pbr::OriginalName("NONE")] None = 0, + [pbr::OriginalName("PNG")] Png = 1, + } + + internal enum ObservationTypeProto { + [pbr::OriginalName("DEFAULT")] Default = 0, + [pbr::OriginalName("GOAL_SIGNAL")] GoalSignal = 1, + } + + #endregion + + #region Messages + internal sealed partial class ObservationProto : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ObservationProto()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Unity.MLAgents.CommunicatorObjects.ObservationReflection.Descriptor.MessageTypes[0]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public ObservationProto() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public ObservationProto(ObservationProto other) : this() { + shape_ = other.shape_.Clone(); + compressionType_ = other.compressionType_; + compressedChannelMapping_ = other.compressedChannelMapping_.Clone(); + dimensionProperties_ = other.dimensionProperties_.Clone(); + observationType_ = other.observationType_; + name_ = other.name_; + switch (other.ObservationDataCase) { + case ObservationDataOneofCase.CompressedData: + CompressedData = other.CompressedData; + break; + case ObservationDataOneofCase.FloatData: + FloatData = other.FloatData.Clone(); + break; + } + + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public ObservationProto Clone() { + return new ObservationProto(this); + } + + /// Field number for the "shape" field. + public const int ShapeFieldNumber = 1; + private static readonly pb::FieldCodec _repeated_shape_codec + = pb::FieldCodec.ForInt32(10); + private readonly pbc::RepeatedField shape_ = new pbc::RepeatedField(); + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public pbc::RepeatedField Shape { + get { return shape_; } + } + + /// Field number for the "compression_type" field. + public const int CompressionTypeFieldNumber = 2; + private global::Unity.MLAgents.CommunicatorObjects.CompressionTypeProto compressionType_ = 0; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Unity.MLAgents.CommunicatorObjects.CompressionTypeProto CompressionType { + get { return compressionType_; } + set { + compressionType_ = value; + } + } + + /// Field number for the "compressed_data" field. + public const int CompressedDataFieldNumber = 3; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public pb::ByteString CompressedData { + get { return observationDataCase_ == ObservationDataOneofCase.CompressedData ? (pb::ByteString) observationData_ : pb::ByteString.Empty; } + set { + observationData_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + observationDataCase_ = ObservationDataOneofCase.CompressedData; + } + } + + /// Field number for the "float_data" field. + public const int FloatDataFieldNumber = 4; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Unity.MLAgents.CommunicatorObjects.ObservationProto.Types.FloatData FloatData { + get { return observationDataCase_ == ObservationDataOneofCase.FloatData ? (global::Unity.MLAgents.CommunicatorObjects.ObservationProto.Types.FloatData) observationData_ : null; } + set { + observationData_ = value; + observationDataCase_ = value == null ? ObservationDataOneofCase.None : ObservationDataOneofCase.FloatData; + } + } + + /// Field number for the "compressed_channel_mapping" field. + public const int CompressedChannelMappingFieldNumber = 5; + private static readonly pb::FieldCodec _repeated_compressedChannelMapping_codec + = pb::FieldCodec.ForInt32(42); + private readonly pbc::RepeatedField compressedChannelMapping_ = new pbc::RepeatedField(); + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public pbc::RepeatedField CompressedChannelMapping { + get { return compressedChannelMapping_; } + } + + /// Field number for the "dimension_properties" field. + public const int DimensionPropertiesFieldNumber = 6; + private static readonly pb::FieldCodec _repeated_dimensionProperties_codec + = pb::FieldCodec.ForInt32(50); + private readonly pbc::RepeatedField dimensionProperties_ = new pbc::RepeatedField(); + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public pbc::RepeatedField DimensionProperties { + get { return dimensionProperties_; } + } + + /// Field number for the "observation_type" field. + public const int ObservationTypeFieldNumber = 7; + private global::Unity.MLAgents.CommunicatorObjects.ObservationTypeProto observationType_ = 0; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Unity.MLAgents.CommunicatorObjects.ObservationTypeProto ObservationType { + get { return observationType_; } + set { + observationType_ = value; + } + } + + /// Field number for the "name" field. + public const int NameFieldNumber = 8; + private string name_ = ""; + /// + /// Optional name of the observation. + /// This will be set to the ISensor name when writing, + /// and read into the ObservationSpec in the low-level API + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public string Name { + get { return name_; } + set { + name_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + private object observationData_; + /// Enum of possible cases for the "observation_data" oneof. + public enum ObservationDataOneofCase { + None = 0, + CompressedData = 3, + FloatData = 4, + } + private ObservationDataOneofCase observationDataCase_ = ObservationDataOneofCase.None; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public ObservationDataOneofCase ObservationDataCase { + get { return observationDataCase_; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void ClearObservationData() { + observationDataCase_ = ObservationDataOneofCase.None; + observationData_ = null; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as ObservationProto); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(ObservationProto other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if(!shape_.Equals(other.shape_)) return false; + if (CompressionType != other.CompressionType) return false; + if (CompressedData != other.CompressedData) return false; + if (!object.Equals(FloatData, other.FloatData)) return false; + if(!compressedChannelMapping_.Equals(other.compressedChannelMapping_)) return false; + if(!dimensionProperties_.Equals(other.dimensionProperties_)) return false; + if (ObservationType != other.ObservationType) return false; + if (Name != other.Name) return false; + if (ObservationDataCase != other.ObservationDataCase) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + hash ^= shape_.GetHashCode(); + if (CompressionType != 0) hash ^= CompressionType.GetHashCode(); + if (observationDataCase_ == ObservationDataOneofCase.CompressedData) hash ^= CompressedData.GetHashCode(); + if (observationDataCase_ == ObservationDataOneofCase.FloatData) hash ^= FloatData.GetHashCode(); + hash ^= compressedChannelMapping_.GetHashCode(); + hash ^= dimensionProperties_.GetHashCode(); + if (ObservationType != 0) hash ^= ObservationType.GetHashCode(); + if (Name.Length != 0) hash ^= Name.GetHashCode(); + hash ^= (int) observationDataCase_; + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + shape_.WriteTo(output, _repeated_shape_codec); + if (CompressionType != 0) { + output.WriteRawTag(16); + output.WriteEnum((int) CompressionType); + } + if (observationDataCase_ == ObservationDataOneofCase.CompressedData) { + output.WriteRawTag(26); + output.WriteBytes(CompressedData); + } + if (observationDataCase_ == ObservationDataOneofCase.FloatData) { + output.WriteRawTag(34); + output.WriteMessage(FloatData); + } + compressedChannelMapping_.WriteTo(output, _repeated_compressedChannelMapping_codec); + dimensionProperties_.WriteTo(output, _repeated_dimensionProperties_codec); + if (ObservationType != 0) { + output.WriteRawTag(56); + output.WriteEnum((int) ObservationType); + } + if (Name.Length != 0) { + output.WriteRawTag(66); + output.WriteString(Name); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + size += shape_.CalculateSize(_repeated_shape_codec); + if (CompressionType != 0) { + size += 1 + pb::CodedOutputStream.ComputeEnumSize((int) CompressionType); + } + if (observationDataCase_ == ObservationDataOneofCase.CompressedData) { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(CompressedData); + } + if (observationDataCase_ == ObservationDataOneofCase.FloatData) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(FloatData); + } + size += compressedChannelMapping_.CalculateSize(_repeated_compressedChannelMapping_codec); + size += dimensionProperties_.CalculateSize(_repeated_dimensionProperties_codec); + if (ObservationType != 0) { + size += 1 + pb::CodedOutputStream.ComputeEnumSize((int) ObservationType); + } + if (Name.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Name); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(ObservationProto other) { + if (other == null) { + return; + } + shape_.Add(other.shape_); + if (other.CompressionType != 0) { + CompressionType = other.CompressionType; + } + compressedChannelMapping_.Add(other.compressedChannelMapping_); + dimensionProperties_.Add(other.dimensionProperties_); + if (other.ObservationType != 0) { + ObservationType = other.ObservationType; + } + if (other.Name.Length != 0) { + Name = other.Name; + } + switch (other.ObservationDataCase) { + case ObservationDataOneofCase.CompressedData: + CompressedData = other.CompressedData; + break; + case ObservationDataOneofCase.FloatData: + if (FloatData == null) { + FloatData = new global::Unity.MLAgents.CommunicatorObjects.ObservationProto.Types.FloatData(); + } + FloatData.MergeFrom(other.FloatData); + break; + } + + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: + case 8: { + shape_.AddEntriesFrom(input, _repeated_shape_codec); + break; + } + case 16: { + compressionType_ = (global::Unity.MLAgents.CommunicatorObjects.CompressionTypeProto) input.ReadEnum(); + break; + } + case 26: { + CompressedData = input.ReadBytes(); + break; + } + case 34: { + global::Unity.MLAgents.CommunicatorObjects.ObservationProto.Types.FloatData subBuilder = new global::Unity.MLAgents.CommunicatorObjects.ObservationProto.Types.FloatData(); + if (observationDataCase_ == ObservationDataOneofCase.FloatData) { + subBuilder.MergeFrom(FloatData); + } + input.ReadMessage(subBuilder); + FloatData = subBuilder; + break; + } + case 42: + case 40: { + compressedChannelMapping_.AddEntriesFrom(input, _repeated_compressedChannelMapping_codec); + break; + } + case 50: + case 48: { + dimensionProperties_.AddEntriesFrom(input, _repeated_dimensionProperties_codec); + break; + } + case 56: { + observationType_ = (global::Unity.MLAgents.CommunicatorObjects.ObservationTypeProto) input.ReadEnum(); + break; + } + case 66: { + Name = input.ReadString(); + break; + } + } + } + } + + #region Nested types + /// Container for nested types declared in the ObservationProto message type. + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static partial class Types { + internal sealed partial class FloatData : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new FloatData()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Unity.MLAgents.CommunicatorObjects.ObservationProto.Descriptor.NestedTypes[0]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public FloatData() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public FloatData(FloatData other) : this() { + data_ = other.data_.Clone(); + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public FloatData Clone() { + return new FloatData(this); + } + + /// Field number for the "data" field. + public const int DataFieldNumber = 1; + private static readonly pb::FieldCodec _repeated_data_codec + = pb::FieldCodec.ForFloat(10); + private readonly pbc::RepeatedField data_ = new pbc::RepeatedField(); + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public pbc::RepeatedField Data { + get { return data_; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as FloatData); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(FloatData other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if(!data_.Equals(other.data_)) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + hash ^= data_.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + data_.WriteTo(output, _repeated_data_codec); + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + size += data_.CalculateSize(_repeated_data_codec); + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(FloatData other) { + if (other == null) { + return; + } + data_.Add(other.data_); + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: + case 13: { + data_.AddEntriesFrom(input, _repeated_data_codec); + break; + } + } + } + } + + } + + } + #endregion + + } + + #endregion + +} + +#endregion Designer generated code diff --git a/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/Observation.cs.meta b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/Observation.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..bb317b4f53dcfee614bdcb3d670f6f7f35c519b7 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/Observation.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/SpaceType.cs b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/SpaceType.cs new file mode 100644 index 0000000000000000000000000000000000000000..d3bf7cf22002df033bf5e928c88fe6dce4bae192 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/SpaceType.cs @@ -0,0 +1,48 @@ +// +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: mlagents_envs/communicator_objects/space_type.proto +// +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = global::Google.Protobuf; +using pbc = global::Google.Protobuf.Collections; +using pbr = global::Google.Protobuf.Reflection; +using scg = global::System.Collections.Generic; +namespace Unity.MLAgents.CommunicatorObjects { + + /// Holder for reflection information generated from mlagents_envs/communicator_objects/space_type.proto + internal static partial class SpaceTypeReflection { + + #region Descriptor + /// File descriptor for mlagents_envs/communicator_objects/space_type.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static SpaceTypeReflection() { + byte[] descriptorData = global::System.Convert.FromBase64String( + string.Concat( + "CjNtbGFnZW50c19lbnZzL2NvbW11bmljYXRvcl9vYmplY3RzL3NwYWNlX3R5", + "cGUucHJvdG8SFGNvbW11bmljYXRvcl9vYmplY3RzKi4KDlNwYWNlVHlwZVBy", + "b3RvEgwKCGRpc2NyZXRlEAASDgoKY29udGludW91cxABQiWqAiJVbml0eS5N", + "TEFnZW50cy5Db21tdW5pY2F0b3JPYmplY3RzYgZwcm90bzM=")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { }, + new pbr::GeneratedClrTypeInfo(new[] {typeof(global::Unity.MLAgents.CommunicatorObjects.SpaceTypeProto), }, null)); + } + #endregion + + } + #region Enums + internal enum SpaceTypeProto { + [pbr::OriginalName("discrete")] Discrete = 0, + [pbr::OriginalName("continuous")] Continuous = 1, + } + + #endregion + +} + +#endregion Designer generated code diff --git a/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/SpaceType.cs.meta b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/SpaceType.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..4ca81201e47406bd8ff526eac4e17a0584f3952b Binary files /dev/null and b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/SpaceType.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/TrainingAnalytics.cs b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/TrainingAnalytics.cs new file mode 100644 index 0000000000000000000000000000000000000000..042357f28062745fc7ce0c1067235a6f8d17c023 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/TrainingAnalytics.cs @@ -0,0 +1,907 @@ +// +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: mlagents_envs/communicator_objects/training_analytics.proto +// +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = global::Google.Protobuf; +using pbc = global::Google.Protobuf.Collections; +using pbr = global::Google.Protobuf.Reflection; +using scg = global::System.Collections.Generic; +namespace Unity.MLAgents.CommunicatorObjects { + + /// Holder for reflection information generated from mlagents_envs/communicator_objects/training_analytics.proto + internal static partial class TrainingAnalyticsReflection { + + #region Descriptor + /// File descriptor for mlagents_envs/communicator_objects/training_analytics.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static TrainingAnalyticsReflection() { + byte[] descriptorData = global::System.Convert.FromBase64String( + string.Concat( + "CjttbGFnZW50c19lbnZzL2NvbW11bmljYXRvcl9vYmplY3RzL3RyYWluaW5n", + "X2FuYWx5dGljcy5wcm90bxIUY29tbXVuaWNhdG9yX29iamVjdHMi7gEKHlRy", + "YWluaW5nRW52aXJvbm1lbnRJbml0aWFsaXplZBIYChBtbGFnZW50c192ZXJz", + "aW9uGAEgASgJEh0KFW1sYWdlbnRzX2VudnNfdmVyc2lvbhgCIAEoCRIWCg5w", + "eXRob25fdmVyc2lvbhgDIAEoCRIVCg10b3JjaF92ZXJzaW9uGAQgASgJEhkK", + "EXRvcmNoX2RldmljZV90eXBlGAUgASgJEhAKCG51bV9lbnZzGAYgASgFEiIK", + "Gm51bV9lbnZpcm9ubWVudF9wYXJhbWV0ZXJzGAcgASgFEhMKC3J1bl9vcHRp", + "b25zGAggASgJIr0DChtUcmFpbmluZ0JlaGF2aW9ySW5pdGlhbGl6ZWQSFQoN", + "YmVoYXZpb3JfbmFtZRgBIAEoCRIUCgx0cmFpbmVyX3R5cGUYAiABKAkSIAoY", + "ZXh0cmluc2ljX3Jld2FyZF9lbmFibGVkGAMgASgIEhsKE2dhaWxfcmV3YXJk", + "X2VuYWJsZWQYBCABKAgSIAoYY3VyaW9zaXR5X3Jld2FyZF9lbmFibGVkGAUg", + "ASgIEhoKEnJuZF9yZXdhcmRfZW5hYmxlZBgGIAEoCBIiChpiZWhhdmlvcmFs", + "X2Nsb25pbmdfZW5hYmxlZBgHIAEoCBIZChFyZWN1cnJlbnRfZW5hYmxlZBgI", + "IAEoCBIWCg52aXN1YWxfZW5jb2RlchgJIAEoCRIaChJudW1fbmV0d29ya19s", + "YXllcnMYCiABKAUSIAoYbnVtX25ldHdvcmtfaGlkZGVuX3VuaXRzGAsgASgF", + "EhgKEHRyYWluZXJfdGhyZWFkZWQYDCABKAgSGQoRc2VsZl9wbGF5X2VuYWJs", + "ZWQYDSABKAgSGgoSY3VycmljdWx1bV9lbmFibGVkGA4gASgIEg4KBmNvbmZp", + "ZxgPIAEoCUIlqgIiVW5pdHkuTUxBZ2VudHMuQ29tbXVuaWNhdG9yT2JqZWN0", + "c2IGcHJvdG8z")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { }, + new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(global::Unity.MLAgents.CommunicatorObjects.TrainingEnvironmentInitialized), global::Unity.MLAgents.CommunicatorObjects.TrainingEnvironmentInitialized.Parser, new[]{ "MlagentsVersion", "MlagentsEnvsVersion", "PythonVersion", "TorchVersion", "TorchDeviceType", "NumEnvs", "NumEnvironmentParameters", "RunOptions" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Unity.MLAgents.CommunicatorObjects.TrainingBehaviorInitialized), global::Unity.MLAgents.CommunicatorObjects.TrainingBehaviorInitialized.Parser, new[]{ "BehaviorName", "TrainerType", "ExtrinsicRewardEnabled", "GailRewardEnabled", "CuriosityRewardEnabled", "RndRewardEnabled", "BehavioralCloningEnabled", "RecurrentEnabled", "VisualEncoder", "NumNetworkLayers", "NumNetworkHiddenUnits", "TrainerThreaded", "SelfPlayEnabled", "CurriculumEnabled", "Config" }, null, null, null) + })); + } + #endregion + + } + #region Messages + internal sealed partial class TrainingEnvironmentInitialized : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new TrainingEnvironmentInitialized()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Unity.MLAgents.CommunicatorObjects.TrainingAnalyticsReflection.Descriptor.MessageTypes[0]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public TrainingEnvironmentInitialized() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public TrainingEnvironmentInitialized(TrainingEnvironmentInitialized other) : this() { + mlagentsVersion_ = other.mlagentsVersion_; + mlagentsEnvsVersion_ = other.mlagentsEnvsVersion_; + pythonVersion_ = other.pythonVersion_; + torchVersion_ = other.torchVersion_; + torchDeviceType_ = other.torchDeviceType_; + numEnvs_ = other.numEnvs_; + numEnvironmentParameters_ = other.numEnvironmentParameters_; + runOptions_ = other.runOptions_; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public TrainingEnvironmentInitialized Clone() { + return new TrainingEnvironmentInitialized(this); + } + + /// Field number for the "mlagents_version" field. + public const int MlagentsVersionFieldNumber = 1; + private string mlagentsVersion_ = ""; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public string MlagentsVersion { + get { return mlagentsVersion_; } + set { + mlagentsVersion_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "mlagents_envs_version" field. + public const int MlagentsEnvsVersionFieldNumber = 2; + private string mlagentsEnvsVersion_ = ""; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public string MlagentsEnvsVersion { + get { return mlagentsEnvsVersion_; } + set { + mlagentsEnvsVersion_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "python_version" field. + public const int PythonVersionFieldNumber = 3; + private string pythonVersion_ = ""; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public string PythonVersion { + get { return pythonVersion_; } + set { + pythonVersion_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "torch_version" field. + public const int TorchVersionFieldNumber = 4; + private string torchVersion_ = ""; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public string TorchVersion { + get { return torchVersion_; } + set { + torchVersion_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "torch_device_type" field. + public const int TorchDeviceTypeFieldNumber = 5; + private string torchDeviceType_ = ""; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public string TorchDeviceType { + get { return torchDeviceType_; } + set { + torchDeviceType_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "num_envs" field. + public const int NumEnvsFieldNumber = 6; + private int numEnvs_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int NumEnvs { + get { return numEnvs_; } + set { + numEnvs_ = value; + } + } + + /// Field number for the "num_environment_parameters" field. + public const int NumEnvironmentParametersFieldNumber = 7; + private int numEnvironmentParameters_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int NumEnvironmentParameters { + get { return numEnvironmentParameters_; } + set { + numEnvironmentParameters_ = value; + } + } + + /// Field number for the "run_options" field. + public const int RunOptionsFieldNumber = 8; + private string runOptions_ = ""; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public string RunOptions { + get { return runOptions_; } + set { + runOptions_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as TrainingEnvironmentInitialized); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(TrainingEnvironmentInitialized other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (MlagentsVersion != other.MlagentsVersion) return false; + if (MlagentsEnvsVersion != other.MlagentsEnvsVersion) return false; + if (PythonVersion != other.PythonVersion) return false; + if (TorchVersion != other.TorchVersion) return false; + if (TorchDeviceType != other.TorchDeviceType) return false; + if (NumEnvs != other.NumEnvs) return false; + if (NumEnvironmentParameters != other.NumEnvironmentParameters) return false; + if (RunOptions != other.RunOptions) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (MlagentsVersion.Length != 0) hash ^= MlagentsVersion.GetHashCode(); + if (MlagentsEnvsVersion.Length != 0) hash ^= MlagentsEnvsVersion.GetHashCode(); + if (PythonVersion.Length != 0) hash ^= PythonVersion.GetHashCode(); + if (TorchVersion.Length != 0) hash ^= TorchVersion.GetHashCode(); + if (TorchDeviceType.Length != 0) hash ^= TorchDeviceType.GetHashCode(); + if (NumEnvs != 0) hash ^= NumEnvs.GetHashCode(); + if (NumEnvironmentParameters != 0) hash ^= NumEnvironmentParameters.GetHashCode(); + if (RunOptions.Length != 0) hash ^= RunOptions.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (MlagentsVersion.Length != 0) { + output.WriteRawTag(10); + output.WriteString(MlagentsVersion); + } + if (MlagentsEnvsVersion.Length != 0) { + output.WriteRawTag(18); + output.WriteString(MlagentsEnvsVersion); + } + if (PythonVersion.Length != 0) { + output.WriteRawTag(26); + output.WriteString(PythonVersion); + } + if (TorchVersion.Length != 0) { + output.WriteRawTag(34); + output.WriteString(TorchVersion); + } + if (TorchDeviceType.Length != 0) { + output.WriteRawTag(42); + output.WriteString(TorchDeviceType); + } + if (NumEnvs != 0) { + output.WriteRawTag(48); + output.WriteInt32(NumEnvs); + } + if (NumEnvironmentParameters != 0) { + output.WriteRawTag(56); + output.WriteInt32(NumEnvironmentParameters); + } + if (RunOptions.Length != 0) { + output.WriteRawTag(66); + output.WriteString(RunOptions); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (MlagentsVersion.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(MlagentsVersion); + } + if (MlagentsEnvsVersion.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(MlagentsEnvsVersion); + } + if (PythonVersion.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(PythonVersion); + } + if (TorchVersion.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(TorchVersion); + } + if (TorchDeviceType.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(TorchDeviceType); + } + if (NumEnvs != 0) { + size += 1 + pb::CodedOutputStream.ComputeInt32Size(NumEnvs); + } + if (NumEnvironmentParameters != 0) { + size += 1 + pb::CodedOutputStream.ComputeInt32Size(NumEnvironmentParameters); + } + if (RunOptions.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(RunOptions); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(TrainingEnvironmentInitialized other) { + if (other == null) { + return; + } + if (other.MlagentsVersion.Length != 0) { + MlagentsVersion = other.MlagentsVersion; + } + if (other.MlagentsEnvsVersion.Length != 0) { + MlagentsEnvsVersion = other.MlagentsEnvsVersion; + } + if (other.PythonVersion.Length != 0) { + PythonVersion = other.PythonVersion; + } + if (other.TorchVersion.Length != 0) { + TorchVersion = other.TorchVersion; + } + if (other.TorchDeviceType.Length != 0) { + TorchDeviceType = other.TorchDeviceType; + } + if (other.NumEnvs != 0) { + NumEnvs = other.NumEnvs; + } + if (other.NumEnvironmentParameters != 0) { + NumEnvironmentParameters = other.NumEnvironmentParameters; + } + if (other.RunOptions.Length != 0) { + RunOptions = other.RunOptions; + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: { + MlagentsVersion = input.ReadString(); + break; + } + case 18: { + MlagentsEnvsVersion = input.ReadString(); + break; + } + case 26: { + PythonVersion = input.ReadString(); + break; + } + case 34: { + TorchVersion = input.ReadString(); + break; + } + case 42: { + TorchDeviceType = input.ReadString(); + break; + } + case 48: { + NumEnvs = input.ReadInt32(); + break; + } + case 56: { + NumEnvironmentParameters = input.ReadInt32(); + break; + } + case 66: { + RunOptions = input.ReadString(); + break; + } + } + } + } + + } + + internal sealed partial class TrainingBehaviorInitialized : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new TrainingBehaviorInitialized()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Unity.MLAgents.CommunicatorObjects.TrainingAnalyticsReflection.Descriptor.MessageTypes[1]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public TrainingBehaviorInitialized() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public TrainingBehaviorInitialized(TrainingBehaviorInitialized other) : this() { + behaviorName_ = other.behaviorName_; + trainerType_ = other.trainerType_; + extrinsicRewardEnabled_ = other.extrinsicRewardEnabled_; + gailRewardEnabled_ = other.gailRewardEnabled_; + curiosityRewardEnabled_ = other.curiosityRewardEnabled_; + rndRewardEnabled_ = other.rndRewardEnabled_; + behavioralCloningEnabled_ = other.behavioralCloningEnabled_; + recurrentEnabled_ = other.recurrentEnabled_; + visualEncoder_ = other.visualEncoder_; + numNetworkLayers_ = other.numNetworkLayers_; + numNetworkHiddenUnits_ = other.numNetworkHiddenUnits_; + trainerThreaded_ = other.trainerThreaded_; + selfPlayEnabled_ = other.selfPlayEnabled_; + curriculumEnabled_ = other.curriculumEnabled_; + config_ = other.config_; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public TrainingBehaviorInitialized Clone() { + return new TrainingBehaviorInitialized(this); + } + + /// Field number for the "behavior_name" field. + public const int BehaviorNameFieldNumber = 1; + private string behaviorName_ = ""; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public string BehaviorName { + get { return behaviorName_; } + set { + behaviorName_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "trainer_type" field. + public const int TrainerTypeFieldNumber = 2; + private string trainerType_ = ""; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public string TrainerType { + get { return trainerType_; } + set { + trainerType_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "extrinsic_reward_enabled" field. + public const int ExtrinsicRewardEnabledFieldNumber = 3; + private bool extrinsicRewardEnabled_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool ExtrinsicRewardEnabled { + get { return extrinsicRewardEnabled_; } + set { + extrinsicRewardEnabled_ = value; + } + } + + /// Field number for the "gail_reward_enabled" field. + public const int GailRewardEnabledFieldNumber = 4; + private bool gailRewardEnabled_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool GailRewardEnabled { + get { return gailRewardEnabled_; } + set { + gailRewardEnabled_ = value; + } + } + + /// Field number for the "curiosity_reward_enabled" field. + public const int CuriosityRewardEnabledFieldNumber = 5; + private bool curiosityRewardEnabled_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool CuriosityRewardEnabled { + get { return curiosityRewardEnabled_; } + set { + curiosityRewardEnabled_ = value; + } + } + + /// Field number for the "rnd_reward_enabled" field. + public const int RndRewardEnabledFieldNumber = 6; + private bool rndRewardEnabled_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool RndRewardEnabled { + get { return rndRewardEnabled_; } + set { + rndRewardEnabled_ = value; + } + } + + /// Field number for the "behavioral_cloning_enabled" field. + public const int BehavioralCloningEnabledFieldNumber = 7; + private bool behavioralCloningEnabled_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool BehavioralCloningEnabled { + get { return behavioralCloningEnabled_; } + set { + behavioralCloningEnabled_ = value; + } + } + + /// Field number for the "recurrent_enabled" field. + public const int RecurrentEnabledFieldNumber = 8; + private bool recurrentEnabled_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool RecurrentEnabled { + get { return recurrentEnabled_; } + set { + recurrentEnabled_ = value; + } + } + + /// Field number for the "visual_encoder" field. + public const int VisualEncoderFieldNumber = 9; + private string visualEncoder_ = ""; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public string VisualEncoder { + get { return visualEncoder_; } + set { + visualEncoder_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "num_network_layers" field. + public const int NumNetworkLayersFieldNumber = 10; + private int numNetworkLayers_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int NumNetworkLayers { + get { return numNetworkLayers_; } + set { + numNetworkLayers_ = value; + } + } + + /// Field number for the "num_network_hidden_units" field. + public const int NumNetworkHiddenUnitsFieldNumber = 11; + private int numNetworkHiddenUnits_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int NumNetworkHiddenUnits { + get { return numNetworkHiddenUnits_; } + set { + numNetworkHiddenUnits_ = value; + } + } + + /// Field number for the "trainer_threaded" field. + public const int TrainerThreadedFieldNumber = 12; + private bool trainerThreaded_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool TrainerThreaded { + get { return trainerThreaded_; } + set { + trainerThreaded_ = value; + } + } + + /// Field number for the "self_play_enabled" field. + public const int SelfPlayEnabledFieldNumber = 13; + private bool selfPlayEnabled_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool SelfPlayEnabled { + get { return selfPlayEnabled_; } + set { + selfPlayEnabled_ = value; + } + } + + /// Field number for the "curriculum_enabled" field. + public const int CurriculumEnabledFieldNumber = 14; + private bool curriculumEnabled_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool CurriculumEnabled { + get { return curriculumEnabled_; } + set { + curriculumEnabled_ = value; + } + } + + /// Field number for the "config" field. + public const int ConfigFieldNumber = 15; + private string config_ = ""; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public string Config { + get { return config_; } + set { + config_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as TrainingBehaviorInitialized); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(TrainingBehaviorInitialized other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (BehaviorName != other.BehaviorName) return false; + if (TrainerType != other.TrainerType) return false; + if (ExtrinsicRewardEnabled != other.ExtrinsicRewardEnabled) return false; + if (GailRewardEnabled != other.GailRewardEnabled) return false; + if (CuriosityRewardEnabled != other.CuriosityRewardEnabled) return false; + if (RndRewardEnabled != other.RndRewardEnabled) return false; + if (BehavioralCloningEnabled != other.BehavioralCloningEnabled) return false; + if (RecurrentEnabled != other.RecurrentEnabled) return false; + if (VisualEncoder != other.VisualEncoder) return false; + if (NumNetworkLayers != other.NumNetworkLayers) return false; + if (NumNetworkHiddenUnits != other.NumNetworkHiddenUnits) return false; + if (TrainerThreaded != other.TrainerThreaded) return false; + if (SelfPlayEnabled != other.SelfPlayEnabled) return false; + if (CurriculumEnabled != other.CurriculumEnabled) return false; + if (Config != other.Config) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (BehaviorName.Length != 0) hash ^= BehaviorName.GetHashCode(); + if (TrainerType.Length != 0) hash ^= TrainerType.GetHashCode(); + if (ExtrinsicRewardEnabled != false) hash ^= ExtrinsicRewardEnabled.GetHashCode(); + if (GailRewardEnabled != false) hash ^= GailRewardEnabled.GetHashCode(); + if (CuriosityRewardEnabled != false) hash ^= CuriosityRewardEnabled.GetHashCode(); + if (RndRewardEnabled != false) hash ^= RndRewardEnabled.GetHashCode(); + if (BehavioralCloningEnabled != false) hash ^= BehavioralCloningEnabled.GetHashCode(); + if (RecurrentEnabled != false) hash ^= RecurrentEnabled.GetHashCode(); + if (VisualEncoder.Length != 0) hash ^= VisualEncoder.GetHashCode(); + if (NumNetworkLayers != 0) hash ^= NumNetworkLayers.GetHashCode(); + if (NumNetworkHiddenUnits != 0) hash ^= NumNetworkHiddenUnits.GetHashCode(); + if (TrainerThreaded != false) hash ^= TrainerThreaded.GetHashCode(); + if (SelfPlayEnabled != false) hash ^= SelfPlayEnabled.GetHashCode(); + if (CurriculumEnabled != false) hash ^= CurriculumEnabled.GetHashCode(); + if (Config.Length != 0) hash ^= Config.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (BehaviorName.Length != 0) { + output.WriteRawTag(10); + output.WriteString(BehaviorName); + } + if (TrainerType.Length != 0) { + output.WriteRawTag(18); + output.WriteString(TrainerType); + } + if (ExtrinsicRewardEnabled != false) { + output.WriteRawTag(24); + output.WriteBool(ExtrinsicRewardEnabled); + } + if (GailRewardEnabled != false) { + output.WriteRawTag(32); + output.WriteBool(GailRewardEnabled); + } + if (CuriosityRewardEnabled != false) { + output.WriteRawTag(40); + output.WriteBool(CuriosityRewardEnabled); + } + if (RndRewardEnabled != false) { + output.WriteRawTag(48); + output.WriteBool(RndRewardEnabled); + } + if (BehavioralCloningEnabled != false) { + output.WriteRawTag(56); + output.WriteBool(BehavioralCloningEnabled); + } + if (RecurrentEnabled != false) { + output.WriteRawTag(64); + output.WriteBool(RecurrentEnabled); + } + if (VisualEncoder.Length != 0) { + output.WriteRawTag(74); + output.WriteString(VisualEncoder); + } + if (NumNetworkLayers != 0) { + output.WriteRawTag(80); + output.WriteInt32(NumNetworkLayers); + } + if (NumNetworkHiddenUnits != 0) { + output.WriteRawTag(88); + output.WriteInt32(NumNetworkHiddenUnits); + } + if (TrainerThreaded != false) { + output.WriteRawTag(96); + output.WriteBool(TrainerThreaded); + } + if (SelfPlayEnabled != false) { + output.WriteRawTag(104); + output.WriteBool(SelfPlayEnabled); + } + if (CurriculumEnabled != false) { + output.WriteRawTag(112); + output.WriteBool(CurriculumEnabled); + } + if (Config.Length != 0) { + output.WriteRawTag(122); + output.WriteString(Config); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (BehaviorName.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(BehaviorName); + } + if (TrainerType.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(TrainerType); + } + if (ExtrinsicRewardEnabled != false) { + size += 1 + 1; + } + if (GailRewardEnabled != false) { + size += 1 + 1; + } + if (CuriosityRewardEnabled != false) { + size += 1 + 1; + } + if (RndRewardEnabled != false) { + size += 1 + 1; + } + if (BehavioralCloningEnabled != false) { + size += 1 + 1; + } + if (RecurrentEnabled != false) { + size += 1 + 1; + } + if (VisualEncoder.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(VisualEncoder); + } + if (NumNetworkLayers != 0) { + size += 1 + pb::CodedOutputStream.ComputeInt32Size(NumNetworkLayers); + } + if (NumNetworkHiddenUnits != 0) { + size += 1 + pb::CodedOutputStream.ComputeInt32Size(NumNetworkHiddenUnits); + } + if (TrainerThreaded != false) { + size += 1 + 1; + } + if (SelfPlayEnabled != false) { + size += 1 + 1; + } + if (CurriculumEnabled != false) { + size += 1 + 1; + } + if (Config.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Config); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(TrainingBehaviorInitialized other) { + if (other == null) { + return; + } + if (other.BehaviorName.Length != 0) { + BehaviorName = other.BehaviorName; + } + if (other.TrainerType.Length != 0) { + TrainerType = other.TrainerType; + } + if (other.ExtrinsicRewardEnabled != false) { + ExtrinsicRewardEnabled = other.ExtrinsicRewardEnabled; + } + if (other.GailRewardEnabled != false) { + GailRewardEnabled = other.GailRewardEnabled; + } + if (other.CuriosityRewardEnabled != false) { + CuriosityRewardEnabled = other.CuriosityRewardEnabled; + } + if (other.RndRewardEnabled != false) { + RndRewardEnabled = other.RndRewardEnabled; + } + if (other.BehavioralCloningEnabled != false) { + BehavioralCloningEnabled = other.BehavioralCloningEnabled; + } + if (other.RecurrentEnabled != false) { + RecurrentEnabled = other.RecurrentEnabled; + } + if (other.VisualEncoder.Length != 0) { + VisualEncoder = other.VisualEncoder; + } + if (other.NumNetworkLayers != 0) { + NumNetworkLayers = other.NumNetworkLayers; + } + if (other.NumNetworkHiddenUnits != 0) { + NumNetworkHiddenUnits = other.NumNetworkHiddenUnits; + } + if (other.TrainerThreaded != false) { + TrainerThreaded = other.TrainerThreaded; + } + if (other.SelfPlayEnabled != false) { + SelfPlayEnabled = other.SelfPlayEnabled; + } + if (other.CurriculumEnabled != false) { + CurriculumEnabled = other.CurriculumEnabled; + } + if (other.Config.Length != 0) { + Config = other.Config; + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: { + BehaviorName = input.ReadString(); + break; + } + case 18: { + TrainerType = input.ReadString(); + break; + } + case 24: { + ExtrinsicRewardEnabled = input.ReadBool(); + break; + } + case 32: { + GailRewardEnabled = input.ReadBool(); + break; + } + case 40: { + CuriosityRewardEnabled = input.ReadBool(); + break; + } + case 48: { + RndRewardEnabled = input.ReadBool(); + break; + } + case 56: { + BehavioralCloningEnabled = input.ReadBool(); + break; + } + case 64: { + RecurrentEnabled = input.ReadBool(); + break; + } + case 74: { + VisualEncoder = input.ReadString(); + break; + } + case 80: { + NumNetworkLayers = input.ReadInt32(); + break; + } + case 88: { + NumNetworkHiddenUnits = input.ReadInt32(); + break; + } + case 96: { + TrainerThreaded = input.ReadBool(); + break; + } + case 104: { + SelfPlayEnabled = input.ReadBool(); + break; + } + case 112: { + CurriculumEnabled = input.ReadBool(); + break; + } + case 122: { + Config = input.ReadString(); + break; + } + } + } + } + + } + + #endregion + +} + +#endregion Designer generated code diff --git a/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/TrainingAnalytics.cs.meta b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/TrainingAnalytics.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..63f6b846151f347b05c2c6edd2c831f1763bc7cc Binary files /dev/null and b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/TrainingAnalytics.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityInput.cs b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityInput.cs new file mode 100644 index 0000000000000000000000000000000000000000..9497aa0ad8f5caba9513d5d4a80c9fcb22c0f2e2 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityInput.cs @@ -0,0 +1,220 @@ +// +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: mlagents_envs/communicator_objects/unity_input.proto +// +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = global::Google.Protobuf; +using pbc = global::Google.Protobuf.Collections; +using pbr = global::Google.Protobuf.Reflection; +using scg = global::System.Collections.Generic; +namespace Unity.MLAgents.CommunicatorObjects { + + /// Holder for reflection information generated from mlagents_envs/communicator_objects/unity_input.proto + internal static partial class UnityInputReflection { + + #region Descriptor + /// File descriptor for mlagents_envs/communicator_objects/unity_input.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static UnityInputReflection() { + byte[] descriptorData = global::System.Convert.FromBase64String( + string.Concat( + "CjRtbGFnZW50c19lbnZzL2NvbW11bmljYXRvcl9vYmplY3RzL3VuaXR5X2lu", + "cHV0LnByb3RvEhRjb21tdW5pY2F0b3Jfb2JqZWN0cxo3bWxhZ2VudHNfZW52", + "cy9jb21tdW5pY2F0b3Jfb2JqZWN0cy91bml0eV9ybF9pbnB1dC5wcm90bxpG", + "bWxhZ2VudHNfZW52cy9jb21tdW5pY2F0b3Jfb2JqZWN0cy91bml0eV9ybF9p", + "bml0aWFsaXphdGlvbl9pbnB1dC5wcm90byKkAQoPVW5pdHlJbnB1dFByb3Rv", + "EjkKCHJsX2lucHV0GAEgASgLMicuY29tbXVuaWNhdG9yX29iamVjdHMuVW5p", + "dHlSTElucHV0UHJvdG8SVgoXcmxfaW5pdGlhbGl6YXRpb25faW5wdXQYAiAB", + "KAsyNS5jb21tdW5pY2F0b3Jfb2JqZWN0cy5Vbml0eVJMSW5pdGlhbGl6YXRp", + "b25JbnB1dFByb3RvQiWqAiJVbml0eS5NTEFnZW50cy5Db21tdW5pY2F0b3JP", + "YmplY3RzYgZwcm90bzM=")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { global::Unity.MLAgents.CommunicatorObjects.UnityRlInputReflection.Descriptor, global::Unity.MLAgents.CommunicatorObjects.UnityRlInitializationInputReflection.Descriptor, }, + new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(global::Unity.MLAgents.CommunicatorObjects.UnityInputProto), global::Unity.MLAgents.CommunicatorObjects.UnityInputProto.Parser, new[]{ "RlInput", "RlInitializationInput" }, null, null, null) + })); + } + #endregion + + } + #region Messages + internal sealed partial class UnityInputProto : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new UnityInputProto()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Unity.MLAgents.CommunicatorObjects.UnityInputReflection.Descriptor.MessageTypes[0]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public UnityInputProto() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public UnityInputProto(UnityInputProto other) : this() { + RlInput = other.rlInput_ != null ? other.RlInput.Clone() : null; + RlInitializationInput = other.rlInitializationInput_ != null ? other.RlInitializationInput.Clone() : null; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public UnityInputProto Clone() { + return new UnityInputProto(this); + } + + /// Field number for the "rl_input" field. + public const int RlInputFieldNumber = 1; + private global::Unity.MLAgents.CommunicatorObjects.UnityRLInputProto rlInput_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Unity.MLAgents.CommunicatorObjects.UnityRLInputProto RlInput { + get { return rlInput_; } + set { + rlInput_ = value; + } + } + + /// Field number for the "rl_initialization_input" field. + public const int RlInitializationInputFieldNumber = 2; + private global::Unity.MLAgents.CommunicatorObjects.UnityRLInitializationInputProto rlInitializationInput_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Unity.MLAgents.CommunicatorObjects.UnityRLInitializationInputProto RlInitializationInput { + get { return rlInitializationInput_; } + set { + rlInitializationInput_ = value; + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as UnityInputProto); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(UnityInputProto other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(RlInput, other.RlInput)) return false; + if (!object.Equals(RlInitializationInput, other.RlInitializationInput)) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (rlInput_ != null) hash ^= RlInput.GetHashCode(); + if (rlInitializationInput_ != null) hash ^= RlInitializationInput.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (rlInput_ != null) { + output.WriteRawTag(10); + output.WriteMessage(RlInput); + } + if (rlInitializationInput_ != null) { + output.WriteRawTag(18); + output.WriteMessage(RlInitializationInput); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (rlInput_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(RlInput); + } + if (rlInitializationInput_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(RlInitializationInput); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(UnityInputProto other) { + if (other == null) { + return; + } + if (other.rlInput_ != null) { + if (rlInput_ == null) { + rlInput_ = new global::Unity.MLAgents.CommunicatorObjects.UnityRLInputProto(); + } + RlInput.MergeFrom(other.RlInput); + } + if (other.rlInitializationInput_ != null) { + if (rlInitializationInput_ == null) { + rlInitializationInput_ = new global::Unity.MLAgents.CommunicatorObjects.UnityRLInitializationInputProto(); + } + RlInitializationInput.MergeFrom(other.RlInitializationInput); + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: { + if (rlInput_ == null) { + rlInput_ = new global::Unity.MLAgents.CommunicatorObjects.UnityRLInputProto(); + } + input.ReadMessage(rlInput_); + break; + } + case 18: { + if (rlInitializationInput_ == null) { + rlInitializationInput_ = new global::Unity.MLAgents.CommunicatorObjects.UnityRLInitializationInputProto(); + } + input.ReadMessage(rlInitializationInput_); + break; + } + } + } + } + + } + + #endregion + +} + +#endregion Designer generated code diff --git a/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityInput.cs.meta b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityInput.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..8536ecf4542f855655061f5bb7fba1653961817d Binary files /dev/null and b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityInput.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityMessage.cs b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityMessage.cs new file mode 100644 index 0000000000000000000000000000000000000000..b98264dc44a0eaf89d7ae48726c57f4bf884d25a --- /dev/null +++ b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityMessage.cs @@ -0,0 +1,255 @@ +// +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: mlagents_envs/communicator_objects/unity_message.proto +// +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = global::Google.Protobuf; +using pbc = global::Google.Protobuf.Collections; +using pbr = global::Google.Protobuf.Reflection; +using scg = global::System.Collections.Generic; +namespace Unity.MLAgents.CommunicatorObjects { + + /// Holder for reflection information generated from mlagents_envs/communicator_objects/unity_message.proto + internal static partial class UnityMessageReflection { + + #region Descriptor + /// File descriptor for mlagents_envs/communicator_objects/unity_message.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static UnityMessageReflection() { + byte[] descriptorData = global::System.Convert.FromBase64String( + string.Concat( + "CjZtbGFnZW50c19lbnZzL2NvbW11bmljYXRvcl9vYmplY3RzL3VuaXR5X21l", + "c3NhZ2UucHJvdG8SFGNvbW11bmljYXRvcl9vYmplY3RzGjVtbGFnZW50c19l", + "bnZzL2NvbW11bmljYXRvcl9vYmplY3RzL3VuaXR5X291dHB1dC5wcm90bxo0", + "bWxhZ2VudHNfZW52cy9jb21tdW5pY2F0b3Jfb2JqZWN0cy91bml0eV9pbnB1", + "dC5wcm90bxovbWxhZ2VudHNfZW52cy9jb21tdW5pY2F0b3Jfb2JqZWN0cy9o", + "ZWFkZXIucHJvdG8iwAEKEVVuaXR5TWVzc2FnZVByb3RvEjEKBmhlYWRlchgB", + "IAEoCzIhLmNvbW11bmljYXRvcl9vYmplY3RzLkhlYWRlclByb3RvEjwKDHVu", + "aXR5X291dHB1dBgCIAEoCzImLmNvbW11bmljYXRvcl9vYmplY3RzLlVuaXR5", + "T3V0cHV0UHJvdG8SOgoLdW5pdHlfaW5wdXQYAyABKAsyJS5jb21tdW5pY2F0", + "b3Jfb2JqZWN0cy5Vbml0eUlucHV0UHJvdG9CJaoCIlVuaXR5Lk1MQWdlbnRz", + "LkNvbW11bmljYXRvck9iamVjdHNiBnByb3RvMw==")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { global::Unity.MLAgents.CommunicatorObjects.UnityOutputReflection.Descriptor, global::Unity.MLAgents.CommunicatorObjects.UnityInputReflection.Descriptor, global::Unity.MLAgents.CommunicatorObjects.HeaderReflection.Descriptor, }, + new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(global::Unity.MLAgents.CommunicatorObjects.UnityMessageProto), global::Unity.MLAgents.CommunicatorObjects.UnityMessageProto.Parser, new[]{ "Header", "UnityOutput", "UnityInput" }, null, null, null) + })); + } + #endregion + + } + #region Messages + internal sealed partial class UnityMessageProto : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new UnityMessageProto()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Unity.MLAgents.CommunicatorObjects.UnityMessageReflection.Descriptor.MessageTypes[0]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public UnityMessageProto() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public UnityMessageProto(UnityMessageProto other) : this() { + Header = other.header_ != null ? other.Header.Clone() : null; + UnityOutput = other.unityOutput_ != null ? other.UnityOutput.Clone() : null; + UnityInput = other.unityInput_ != null ? other.UnityInput.Clone() : null; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public UnityMessageProto Clone() { + return new UnityMessageProto(this); + } + + /// Field number for the "header" field. + public const int HeaderFieldNumber = 1; + private global::Unity.MLAgents.CommunicatorObjects.HeaderProto header_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Unity.MLAgents.CommunicatorObjects.HeaderProto Header { + get { return header_; } + set { + header_ = value; + } + } + + /// Field number for the "unity_output" field. + public const int UnityOutputFieldNumber = 2; + private global::Unity.MLAgents.CommunicatorObjects.UnityOutputProto unityOutput_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Unity.MLAgents.CommunicatorObjects.UnityOutputProto UnityOutput { + get { return unityOutput_; } + set { + unityOutput_ = value; + } + } + + /// Field number for the "unity_input" field. + public const int UnityInputFieldNumber = 3; + private global::Unity.MLAgents.CommunicatorObjects.UnityInputProto unityInput_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Unity.MLAgents.CommunicatorObjects.UnityInputProto UnityInput { + get { return unityInput_; } + set { + unityInput_ = value; + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as UnityMessageProto); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(UnityMessageProto other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(Header, other.Header)) return false; + if (!object.Equals(UnityOutput, other.UnityOutput)) return false; + if (!object.Equals(UnityInput, other.UnityInput)) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (header_ != null) hash ^= Header.GetHashCode(); + if (unityOutput_ != null) hash ^= UnityOutput.GetHashCode(); + if (unityInput_ != null) hash ^= UnityInput.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (header_ != null) { + output.WriteRawTag(10); + output.WriteMessage(Header); + } + if (unityOutput_ != null) { + output.WriteRawTag(18); + output.WriteMessage(UnityOutput); + } + if (unityInput_ != null) { + output.WriteRawTag(26); + output.WriteMessage(UnityInput); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (header_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Header); + } + if (unityOutput_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(UnityOutput); + } + if (unityInput_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(UnityInput); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(UnityMessageProto other) { + if (other == null) { + return; + } + if (other.header_ != null) { + if (header_ == null) { + header_ = new global::Unity.MLAgents.CommunicatorObjects.HeaderProto(); + } + Header.MergeFrom(other.Header); + } + if (other.unityOutput_ != null) { + if (unityOutput_ == null) { + unityOutput_ = new global::Unity.MLAgents.CommunicatorObjects.UnityOutputProto(); + } + UnityOutput.MergeFrom(other.UnityOutput); + } + if (other.unityInput_ != null) { + if (unityInput_ == null) { + unityInput_ = new global::Unity.MLAgents.CommunicatorObjects.UnityInputProto(); + } + UnityInput.MergeFrom(other.UnityInput); + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: { + if (header_ == null) { + header_ = new global::Unity.MLAgents.CommunicatorObjects.HeaderProto(); + } + input.ReadMessage(header_); + break; + } + case 18: { + if (unityOutput_ == null) { + unityOutput_ = new global::Unity.MLAgents.CommunicatorObjects.UnityOutputProto(); + } + input.ReadMessage(unityOutput_); + break; + } + case 26: { + if (unityInput_ == null) { + unityInput_ = new global::Unity.MLAgents.CommunicatorObjects.UnityInputProto(); + } + input.ReadMessage(unityInput_); + break; + } + } + } + } + + } + + #endregion + +} + +#endregion Designer generated code diff --git a/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityMessage.cs.meta b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityMessage.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..c0a6e877e1b3c1b289d2804cc39c2c6c7dc3c09d Binary files /dev/null and b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityMessage.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityOutput.cs b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityOutput.cs new file mode 100644 index 0000000000000000000000000000000000000000..efb255d25138291a7d69227cd747f1d21a339b3b --- /dev/null +++ b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityOutput.cs @@ -0,0 +1,220 @@ +// +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: mlagents_envs/communicator_objects/unity_output.proto +// +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = global::Google.Protobuf; +using pbc = global::Google.Protobuf.Collections; +using pbr = global::Google.Protobuf.Reflection; +using scg = global::System.Collections.Generic; +namespace Unity.MLAgents.CommunicatorObjects { + + /// Holder for reflection information generated from mlagents_envs/communicator_objects/unity_output.proto + internal static partial class UnityOutputReflection { + + #region Descriptor + /// File descriptor for mlagents_envs/communicator_objects/unity_output.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static UnityOutputReflection() { + byte[] descriptorData = global::System.Convert.FromBase64String( + string.Concat( + "CjVtbGFnZW50c19lbnZzL2NvbW11bmljYXRvcl9vYmplY3RzL3VuaXR5X291", + "dHB1dC5wcm90bxIUY29tbXVuaWNhdG9yX29iamVjdHMaOG1sYWdlbnRzX2Vu", + "dnMvY29tbXVuaWNhdG9yX29iamVjdHMvdW5pdHlfcmxfb3V0cHV0LnByb3Rv", + "GkdtbGFnZW50c19lbnZzL2NvbW11bmljYXRvcl9vYmplY3RzL3VuaXR5X3Js", + "X2luaXRpYWxpemF0aW9uX291dHB1dC5wcm90byKpAQoQVW5pdHlPdXRwdXRQ", + "cm90bxI7CglybF9vdXRwdXQYASABKAsyKC5jb21tdW5pY2F0b3Jfb2JqZWN0", + "cy5Vbml0eVJMT3V0cHV0UHJvdG8SWAoYcmxfaW5pdGlhbGl6YXRpb25fb3V0", + "cHV0GAIgASgLMjYuY29tbXVuaWNhdG9yX29iamVjdHMuVW5pdHlSTEluaXRp", + "YWxpemF0aW9uT3V0cHV0UHJvdG9CJaoCIlVuaXR5Lk1MQWdlbnRzLkNvbW11", + "bmljYXRvck9iamVjdHNiBnByb3RvMw==")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { global::Unity.MLAgents.CommunicatorObjects.UnityRlOutputReflection.Descriptor, global::Unity.MLAgents.CommunicatorObjects.UnityRlInitializationOutputReflection.Descriptor, }, + new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(global::Unity.MLAgents.CommunicatorObjects.UnityOutputProto), global::Unity.MLAgents.CommunicatorObjects.UnityOutputProto.Parser, new[]{ "RlOutput", "RlInitializationOutput" }, null, null, null) + })); + } + #endregion + + } + #region Messages + internal sealed partial class UnityOutputProto : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new UnityOutputProto()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Unity.MLAgents.CommunicatorObjects.UnityOutputReflection.Descriptor.MessageTypes[0]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public UnityOutputProto() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public UnityOutputProto(UnityOutputProto other) : this() { + RlOutput = other.rlOutput_ != null ? other.RlOutput.Clone() : null; + RlInitializationOutput = other.rlInitializationOutput_ != null ? other.RlInitializationOutput.Clone() : null; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public UnityOutputProto Clone() { + return new UnityOutputProto(this); + } + + /// Field number for the "rl_output" field. + public const int RlOutputFieldNumber = 1; + private global::Unity.MLAgents.CommunicatorObjects.UnityRLOutputProto rlOutput_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Unity.MLAgents.CommunicatorObjects.UnityRLOutputProto RlOutput { + get { return rlOutput_; } + set { + rlOutput_ = value; + } + } + + /// Field number for the "rl_initialization_output" field. + public const int RlInitializationOutputFieldNumber = 2; + private global::Unity.MLAgents.CommunicatorObjects.UnityRLInitializationOutputProto rlInitializationOutput_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Unity.MLAgents.CommunicatorObjects.UnityRLInitializationOutputProto RlInitializationOutput { + get { return rlInitializationOutput_; } + set { + rlInitializationOutput_ = value; + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as UnityOutputProto); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(UnityOutputProto other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(RlOutput, other.RlOutput)) return false; + if (!object.Equals(RlInitializationOutput, other.RlInitializationOutput)) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (rlOutput_ != null) hash ^= RlOutput.GetHashCode(); + if (rlInitializationOutput_ != null) hash ^= RlInitializationOutput.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (rlOutput_ != null) { + output.WriteRawTag(10); + output.WriteMessage(RlOutput); + } + if (rlInitializationOutput_ != null) { + output.WriteRawTag(18); + output.WriteMessage(RlInitializationOutput); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (rlOutput_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(RlOutput); + } + if (rlInitializationOutput_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(RlInitializationOutput); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(UnityOutputProto other) { + if (other == null) { + return; + } + if (other.rlOutput_ != null) { + if (rlOutput_ == null) { + rlOutput_ = new global::Unity.MLAgents.CommunicatorObjects.UnityRLOutputProto(); + } + RlOutput.MergeFrom(other.RlOutput); + } + if (other.rlInitializationOutput_ != null) { + if (rlInitializationOutput_ == null) { + rlInitializationOutput_ = new global::Unity.MLAgents.CommunicatorObjects.UnityRLInitializationOutputProto(); + } + RlInitializationOutput.MergeFrom(other.RlInitializationOutput); + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: { + if (rlOutput_ == null) { + rlOutput_ = new global::Unity.MLAgents.CommunicatorObjects.UnityRLOutputProto(); + } + input.ReadMessage(rlOutput_); + break; + } + case 18: { + if (rlInitializationOutput_ == null) { + rlInitializationOutput_ = new global::Unity.MLAgents.CommunicatorObjects.UnityRLInitializationOutputProto(); + } + input.ReadMessage(rlInitializationOutput_); + break; + } + } + } + } + + } + + #endregion + +} + +#endregion Designer generated code diff --git a/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityOutput.cs.meta b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityOutput.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..2a74abca8f4c5c97021c88f93b00e91be11edae9 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityOutput.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityRlInitializationInput.cs b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityRlInitializationInput.cs new file mode 100644 index 0000000000000000000000000000000000000000..1b83a17e6859b098393eb0310e89d8fb60515a94 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityRlInitializationInput.cs @@ -0,0 +1,312 @@ +// +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: mlagents_envs/communicator_objects/unity_rl_initialization_input.proto +// +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = global::Google.Protobuf; +using pbc = global::Google.Protobuf.Collections; +using pbr = global::Google.Protobuf.Reflection; +using scg = global::System.Collections.Generic; +namespace Unity.MLAgents.CommunicatorObjects { + + /// Holder for reflection information generated from mlagents_envs/communicator_objects/unity_rl_initialization_input.proto + internal static partial class UnityRlInitializationInputReflection { + + #region Descriptor + /// File descriptor for mlagents_envs/communicator_objects/unity_rl_initialization_input.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static UnityRlInitializationInputReflection() { + byte[] descriptorData = global::System.Convert.FromBase64String( + string.Concat( + "CkZtbGFnZW50c19lbnZzL2NvbW11bmljYXRvcl9vYmplY3RzL3VuaXR5X3Js", + "X2luaXRpYWxpemF0aW9uX2lucHV0LnByb3RvEhRjb21tdW5pY2F0b3Jfb2Jq", + "ZWN0cxo1bWxhZ2VudHNfZW52cy9jb21tdW5pY2F0b3Jfb2JqZWN0cy9jYXBh", + "YmlsaXRpZXMucHJvdG8iwAEKH1VuaXR5UkxJbml0aWFsaXphdGlvbklucHV0", + "UHJvdG8SDAoEc2VlZBgBIAEoBRIdChVjb21tdW5pY2F0aW9uX3ZlcnNpb24Y", + "AiABKAkSFwoPcGFja2FnZV92ZXJzaW9uGAMgASgJEkQKDGNhcGFiaWxpdGll", + "cxgEIAEoCzIuLmNvbW11bmljYXRvcl9vYmplY3RzLlVuaXR5UkxDYXBhYmls", + "aXRpZXNQcm90bxIRCgludW1fYXJlYXMYBSABKAVCJaoCIlVuaXR5Lk1MQWdl", + "bnRzLkNvbW11bmljYXRvck9iamVjdHNiBnByb3RvMw==")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { global::Unity.MLAgents.CommunicatorObjects.CapabilitiesReflection.Descriptor, }, + new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(global::Unity.MLAgents.CommunicatorObjects.UnityRLInitializationInputProto), global::Unity.MLAgents.CommunicatorObjects.UnityRLInitializationInputProto.Parser, new[]{ "Seed", "CommunicationVersion", "PackageVersion", "Capabilities", "NumAreas" }, null, null, null) + })); + } + #endregion + + } + #region Messages + /// + /// The initializaiton message - this is typically sent from the Python trainer to the C# environment. + /// + internal sealed partial class UnityRLInitializationInputProto : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new UnityRLInitializationInputProto()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Unity.MLAgents.CommunicatorObjects.UnityRlInitializationInputReflection.Descriptor.MessageTypes[0]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public UnityRLInitializationInputProto() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public UnityRLInitializationInputProto(UnityRLInitializationInputProto other) : this() { + seed_ = other.seed_; + communicationVersion_ = other.communicationVersion_; + packageVersion_ = other.packageVersion_; + Capabilities = other.capabilities_ != null ? other.Capabilities.Clone() : null; + numAreas_ = other.numAreas_; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public UnityRLInitializationInputProto Clone() { + return new UnityRLInitializationInputProto(this); + } + + /// Field number for the "seed" field. + public const int SeedFieldNumber = 1; + private int seed_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int Seed { + get { return seed_; } + set { + seed_ = value; + } + } + + /// Field number for the "communication_version" field. + public const int CommunicationVersionFieldNumber = 2; + private string communicationVersion_ = ""; + /// + /// Communication protocol version that the initiating side (typically the Python trainer) is using. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public string CommunicationVersion { + get { return communicationVersion_; } + set { + communicationVersion_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "package_version" field. + public const int PackageVersionFieldNumber = 3; + private string packageVersion_ = ""; + /// + /// Package/library version that the initiating side (typically the Python trainer) is using. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public string PackageVersion { + get { return packageVersion_; } + set { + packageVersion_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "capabilities" field. + public const int CapabilitiesFieldNumber = 4; + private global::Unity.MLAgents.CommunicatorObjects.UnityRLCapabilitiesProto capabilities_; + /// + /// The RL Capabilities of the Python trainer. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Unity.MLAgents.CommunicatorObjects.UnityRLCapabilitiesProto Capabilities { + get { return capabilities_; } + set { + capabilities_ = value; + } + } + + /// Field number for the "num_areas" field. + public const int NumAreasFieldNumber = 5; + private int numAreas_; + /// + /// The number of training areas to instantiate + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int NumAreas { + get { return numAreas_; } + set { + numAreas_ = value; + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as UnityRLInitializationInputProto); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(UnityRLInitializationInputProto other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (Seed != other.Seed) return false; + if (CommunicationVersion != other.CommunicationVersion) return false; + if (PackageVersion != other.PackageVersion) return false; + if (!object.Equals(Capabilities, other.Capabilities)) return false; + if (NumAreas != other.NumAreas) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (Seed != 0) hash ^= Seed.GetHashCode(); + if (CommunicationVersion.Length != 0) hash ^= CommunicationVersion.GetHashCode(); + if (PackageVersion.Length != 0) hash ^= PackageVersion.GetHashCode(); + if (capabilities_ != null) hash ^= Capabilities.GetHashCode(); + if (NumAreas != 0) hash ^= NumAreas.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (Seed != 0) { + output.WriteRawTag(8); + output.WriteInt32(Seed); + } + if (CommunicationVersion.Length != 0) { + output.WriteRawTag(18); + output.WriteString(CommunicationVersion); + } + if (PackageVersion.Length != 0) { + output.WriteRawTag(26); + output.WriteString(PackageVersion); + } + if (capabilities_ != null) { + output.WriteRawTag(34); + output.WriteMessage(Capabilities); + } + if (NumAreas != 0) { + output.WriteRawTag(40); + output.WriteInt32(NumAreas); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (Seed != 0) { + size += 1 + pb::CodedOutputStream.ComputeInt32Size(Seed); + } + if (CommunicationVersion.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(CommunicationVersion); + } + if (PackageVersion.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(PackageVersion); + } + if (capabilities_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Capabilities); + } + if (NumAreas != 0) { + size += 1 + pb::CodedOutputStream.ComputeInt32Size(NumAreas); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(UnityRLInitializationInputProto other) { + if (other == null) { + return; + } + if (other.Seed != 0) { + Seed = other.Seed; + } + if (other.CommunicationVersion.Length != 0) { + CommunicationVersion = other.CommunicationVersion; + } + if (other.PackageVersion.Length != 0) { + PackageVersion = other.PackageVersion; + } + if (other.capabilities_ != null) { + if (capabilities_ == null) { + capabilities_ = new global::Unity.MLAgents.CommunicatorObjects.UnityRLCapabilitiesProto(); + } + Capabilities.MergeFrom(other.Capabilities); + } + if (other.NumAreas != 0) { + NumAreas = other.NumAreas; + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 8: { + Seed = input.ReadInt32(); + break; + } + case 18: { + CommunicationVersion = input.ReadString(); + break; + } + case 26: { + PackageVersion = input.ReadString(); + break; + } + case 34: { + if (capabilities_ == null) { + capabilities_ = new global::Unity.MLAgents.CommunicatorObjects.UnityRLCapabilitiesProto(); + } + input.ReadMessage(capabilities_); + break; + } + case 40: { + NumAreas = input.ReadInt32(); + break; + } + } + } + } + + } + + #endregion + +} + +#endregion Designer generated code diff --git a/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityRlInitializationInput.cs.meta b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityRlInitializationInput.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..3f6574ca279d0b66119a155dd737cb705375f2b3 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityRlInitializationInput.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityRlInitializationOutput.cs b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityRlInitializationOutput.cs new file mode 100644 index 0000000000000000000000000000000000000000..1e073a596532f18412453948ab6323a49babcaab --- /dev/null +++ b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityRlInitializationOutput.cs @@ -0,0 +1,332 @@ +// +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: mlagents_envs/communicator_objects/unity_rl_initialization_output.proto +// +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = global::Google.Protobuf; +using pbc = global::Google.Protobuf.Collections; +using pbr = global::Google.Protobuf.Reflection; +using scg = global::System.Collections.Generic; +namespace Unity.MLAgents.CommunicatorObjects { + + /// Holder for reflection information generated from mlagents_envs/communicator_objects/unity_rl_initialization_output.proto + internal static partial class UnityRlInitializationOutputReflection { + + #region Descriptor + /// File descriptor for mlagents_envs/communicator_objects/unity_rl_initialization_output.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static UnityRlInitializationOutputReflection() { + byte[] descriptorData = global::System.Convert.FromBase64String( + string.Concat( + "CkdtbGFnZW50c19lbnZzL2NvbW11bmljYXRvcl9vYmplY3RzL3VuaXR5X3Js", + "X2luaXRpYWxpemF0aW9uX291dHB1dC5wcm90bxIUY29tbXVuaWNhdG9yX29i", + "amVjdHMaNW1sYWdlbnRzX2VudnMvY29tbXVuaWNhdG9yX29iamVjdHMvY2Fw", + "YWJpbGl0aWVzLnByb3RvGjltbGFnZW50c19lbnZzL2NvbW11bmljYXRvcl9v", + "YmplY3RzL2JyYWluX3BhcmFtZXRlcnMucHJvdG8ijAIKIFVuaXR5UkxJbml0", + "aWFsaXphdGlvbk91dHB1dFByb3RvEgwKBG5hbWUYASABKAkSHQoVY29tbXVu", + "aWNhdGlvbl92ZXJzaW9uGAIgASgJEhAKCGxvZ19wYXRoGAMgASgJEkQKEGJy", + "YWluX3BhcmFtZXRlcnMYBSADKAsyKi5jb21tdW5pY2F0b3Jfb2JqZWN0cy5C", + "cmFpblBhcmFtZXRlcnNQcm90bxIXCg9wYWNrYWdlX3ZlcnNpb24YByABKAkS", + "RAoMY2FwYWJpbGl0aWVzGAggASgLMi4uY29tbXVuaWNhdG9yX29iamVjdHMu", + "VW5pdHlSTENhcGFiaWxpdGllc1Byb3RvSgQIBhAHQiWqAiJVbml0eS5NTEFn", + "ZW50cy5Db21tdW5pY2F0b3JPYmplY3RzYgZwcm90bzM=")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { global::Unity.MLAgents.CommunicatorObjects.CapabilitiesReflection.Descriptor, global::Unity.MLAgents.CommunicatorObjects.BrainParametersReflection.Descriptor, }, + new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(global::Unity.MLAgents.CommunicatorObjects.UnityRLInitializationOutputProto), global::Unity.MLAgents.CommunicatorObjects.UnityRLInitializationOutputProto.Parser, new[]{ "Name", "CommunicationVersion", "LogPath", "BrainParameters", "PackageVersion", "Capabilities" }, null, null, null) + })); + } + #endregion + + } + #region Messages + /// + /// The request message containing the academy's parameters. + /// + internal sealed partial class UnityRLInitializationOutputProto : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new UnityRLInitializationOutputProto()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Unity.MLAgents.CommunicatorObjects.UnityRlInitializationOutputReflection.Descriptor.MessageTypes[0]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public UnityRLInitializationOutputProto() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public UnityRLInitializationOutputProto(UnityRLInitializationOutputProto other) : this() { + name_ = other.name_; + communicationVersion_ = other.communicationVersion_; + logPath_ = other.logPath_; + brainParameters_ = other.brainParameters_.Clone(); + packageVersion_ = other.packageVersion_; + Capabilities = other.capabilities_ != null ? other.Capabilities.Clone() : null; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public UnityRLInitializationOutputProto Clone() { + return new UnityRLInitializationOutputProto(this); + } + + /// Field number for the "name" field. + public const int NameFieldNumber = 1; + private string name_ = ""; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public string Name { + get { return name_; } + set { + name_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "communication_version" field. + public const int CommunicationVersionFieldNumber = 2; + private string communicationVersion_ = ""; + /// + /// Communication protocol version that the responding side (typically the C# environment) is using. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public string CommunicationVersion { + get { return communicationVersion_; } + set { + communicationVersion_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "log_path" field. + public const int LogPathFieldNumber = 3; + private string logPath_ = ""; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public string LogPath { + get { return logPath_; } + set { + logPath_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "brain_parameters" field. + public const int BrainParametersFieldNumber = 5; + private static readonly pb::FieldCodec _repeated_brainParameters_codec + = pb::FieldCodec.ForMessage(42, global::Unity.MLAgents.CommunicatorObjects.BrainParametersProto.Parser); + private readonly pbc::RepeatedField brainParameters_ = new pbc::RepeatedField(); + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public pbc::RepeatedField BrainParameters { + get { return brainParameters_; } + } + + /// Field number for the "package_version" field. + public const int PackageVersionFieldNumber = 7; + private string packageVersion_ = ""; + /// + /// Package/library version that the responding side (typically the C# environment) is using. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public string PackageVersion { + get { return packageVersion_; } + set { + packageVersion_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "capabilities" field. + public const int CapabilitiesFieldNumber = 8; + private global::Unity.MLAgents.CommunicatorObjects.UnityRLCapabilitiesProto capabilities_; + /// + /// The RL Capabilities of the C# package. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Unity.MLAgents.CommunicatorObjects.UnityRLCapabilitiesProto Capabilities { + get { return capabilities_; } + set { + capabilities_ = value; + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as UnityRLInitializationOutputProto); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(UnityRLInitializationOutputProto other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (Name != other.Name) return false; + if (CommunicationVersion != other.CommunicationVersion) return false; + if (LogPath != other.LogPath) return false; + if(!brainParameters_.Equals(other.brainParameters_)) return false; + if (PackageVersion != other.PackageVersion) return false; + if (!object.Equals(Capabilities, other.Capabilities)) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (Name.Length != 0) hash ^= Name.GetHashCode(); + if (CommunicationVersion.Length != 0) hash ^= CommunicationVersion.GetHashCode(); + if (LogPath.Length != 0) hash ^= LogPath.GetHashCode(); + hash ^= brainParameters_.GetHashCode(); + if (PackageVersion.Length != 0) hash ^= PackageVersion.GetHashCode(); + if (capabilities_ != null) hash ^= Capabilities.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (Name.Length != 0) { + output.WriteRawTag(10); + output.WriteString(Name); + } + if (CommunicationVersion.Length != 0) { + output.WriteRawTag(18); + output.WriteString(CommunicationVersion); + } + if (LogPath.Length != 0) { + output.WriteRawTag(26); + output.WriteString(LogPath); + } + brainParameters_.WriteTo(output, _repeated_brainParameters_codec); + if (PackageVersion.Length != 0) { + output.WriteRawTag(58); + output.WriteString(PackageVersion); + } + if (capabilities_ != null) { + output.WriteRawTag(66); + output.WriteMessage(Capabilities); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (Name.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Name); + } + if (CommunicationVersion.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(CommunicationVersion); + } + if (LogPath.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(LogPath); + } + size += brainParameters_.CalculateSize(_repeated_brainParameters_codec); + if (PackageVersion.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(PackageVersion); + } + if (capabilities_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Capabilities); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(UnityRLInitializationOutputProto other) { + if (other == null) { + return; + } + if (other.Name.Length != 0) { + Name = other.Name; + } + if (other.CommunicationVersion.Length != 0) { + CommunicationVersion = other.CommunicationVersion; + } + if (other.LogPath.Length != 0) { + LogPath = other.LogPath; + } + brainParameters_.Add(other.brainParameters_); + if (other.PackageVersion.Length != 0) { + PackageVersion = other.PackageVersion; + } + if (other.capabilities_ != null) { + if (capabilities_ == null) { + capabilities_ = new global::Unity.MLAgents.CommunicatorObjects.UnityRLCapabilitiesProto(); + } + Capabilities.MergeFrom(other.Capabilities); + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: { + Name = input.ReadString(); + break; + } + case 18: { + CommunicationVersion = input.ReadString(); + break; + } + case 26: { + LogPath = input.ReadString(); + break; + } + case 42: { + brainParameters_.AddEntriesFrom(input, _repeated_brainParameters_codec); + break; + } + case 58: { + PackageVersion = input.ReadString(); + break; + } + case 66: { + if (capabilities_ == null) { + capabilities_ = new global::Unity.MLAgents.CommunicatorObjects.UnityRLCapabilitiesProto(); + } + input.ReadMessage(capabilities_); + break; + } + } + } + } + + } + + #endregion + +} + +#endregion Designer generated code diff --git a/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityRlInitializationOutput.cs.meta b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityRlInitializationOutput.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..3b3483a2302146be311a813fd509a4bc8acca2d2 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityRlInitializationOutput.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityRlInput.cs b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityRlInput.cs new file mode 100644 index 0000000000000000000000000000000000000000..e8553e8b8eae85d473390c3103dee1084cfd7fdb --- /dev/null +++ b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityRlInput.cs @@ -0,0 +1,361 @@ +// +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: mlagents_envs/communicator_objects/unity_rl_input.proto +// +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = global::Google.Protobuf; +using pbc = global::Google.Protobuf.Collections; +using pbr = global::Google.Protobuf.Reflection; +using scg = global::System.Collections.Generic; +namespace Unity.MLAgents.CommunicatorObjects { + + /// Holder for reflection information generated from mlagents_envs/communicator_objects/unity_rl_input.proto + internal static partial class UnityRlInputReflection { + + #region Descriptor + /// File descriptor for mlagents_envs/communicator_objects/unity_rl_input.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static UnityRlInputReflection() { + byte[] descriptorData = global::System.Convert.FromBase64String( + string.Concat( + "CjdtbGFnZW50c19lbnZzL2NvbW11bmljYXRvcl9vYmplY3RzL3VuaXR5X3Js", + "X2lucHV0LnByb3RvEhRjb21tdW5pY2F0b3Jfb2JqZWN0cxo1bWxhZ2VudHNf", + "ZW52cy9jb21tdW5pY2F0b3Jfb2JqZWN0cy9hZ2VudF9hY3Rpb24ucHJvdG8a", + "MG1sYWdlbnRzX2VudnMvY29tbXVuaWNhdG9yX29iamVjdHMvY29tbWFuZC5w", + "cm90byL+AgoRVW5pdHlSTElucHV0UHJvdG8SUAoNYWdlbnRfYWN0aW9ucxgB", + "IAMoCzI5LmNvbW11bmljYXRvcl9vYmplY3RzLlVuaXR5UkxJbnB1dFByb3Rv", + "LkFnZW50QWN0aW9uc0VudHJ5EjMKB2NvbW1hbmQYBCABKA4yIi5jb21tdW5p", + "Y2F0b3Jfb2JqZWN0cy5Db21tYW5kUHJvdG8SFAoMc2lkZV9jaGFubmVsGAUg", + "ASgMGk0KFExpc3RBZ2VudEFjdGlvblByb3RvEjUKBXZhbHVlGAEgAygLMiYu", + "Y29tbXVuaWNhdG9yX29iamVjdHMuQWdlbnRBY3Rpb25Qcm90bxpxChFBZ2Vu", + "dEFjdGlvbnNFbnRyeRILCgNrZXkYASABKAkSSwoFdmFsdWUYAiABKAsyPC5j", + "b21tdW5pY2F0b3Jfb2JqZWN0cy5Vbml0eVJMSW5wdXRQcm90by5MaXN0QWdl", + "bnRBY3Rpb25Qcm90bzoCOAFKBAgCEANKBAgDEARCJaoCIlVuaXR5Lk1MQWdl", + "bnRzLkNvbW11bmljYXRvck9iamVjdHNiBnByb3RvMw==")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { global::Unity.MLAgents.CommunicatorObjects.AgentActionReflection.Descriptor, global::Unity.MLAgents.CommunicatorObjects.CommandReflection.Descriptor, }, + new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(global::Unity.MLAgents.CommunicatorObjects.UnityRLInputProto), global::Unity.MLAgents.CommunicatorObjects.UnityRLInputProto.Parser, new[]{ "AgentActions", "Command", "SideChannel" }, null, null, new pbr::GeneratedClrTypeInfo[] { new pbr::GeneratedClrTypeInfo(typeof(global::Unity.MLAgents.CommunicatorObjects.UnityRLInputProto.Types.ListAgentActionProto), global::Unity.MLAgents.CommunicatorObjects.UnityRLInputProto.Types.ListAgentActionProto.Parser, new[]{ "Value" }, null, null, null), + null, }) + })); + } + #endregion + + } + #region Messages + internal sealed partial class UnityRLInputProto : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new UnityRLInputProto()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Unity.MLAgents.CommunicatorObjects.UnityRlInputReflection.Descriptor.MessageTypes[0]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public UnityRLInputProto() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public UnityRLInputProto(UnityRLInputProto other) : this() { + agentActions_ = other.agentActions_.Clone(); + command_ = other.command_; + sideChannel_ = other.sideChannel_; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public UnityRLInputProto Clone() { + return new UnityRLInputProto(this); + } + + /// Field number for the "agent_actions" field. + public const int AgentActionsFieldNumber = 1; + private static readonly pbc::MapField.Codec _map_agentActions_codec + = new pbc::MapField.Codec(pb::FieldCodec.ForString(10), pb::FieldCodec.ForMessage(18, global::Unity.MLAgents.CommunicatorObjects.UnityRLInputProto.Types.ListAgentActionProto.Parser), 10); + private readonly pbc::MapField agentActions_ = new pbc::MapField(); + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public pbc::MapField AgentActions { + get { return agentActions_; } + } + + /// Field number for the "command" field. + public const int CommandFieldNumber = 4; + private global::Unity.MLAgents.CommunicatorObjects.CommandProto command_ = 0; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Unity.MLAgents.CommunicatorObjects.CommandProto Command { + get { return command_; } + set { + command_ = value; + } + } + + /// Field number for the "side_channel" field. + public const int SideChannelFieldNumber = 5; + private pb::ByteString sideChannel_ = pb::ByteString.Empty; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public pb::ByteString SideChannel { + get { return sideChannel_; } + set { + sideChannel_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as UnityRLInputProto); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(UnityRLInputProto other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!AgentActions.Equals(other.AgentActions)) return false; + if (Command != other.Command) return false; + if (SideChannel != other.SideChannel) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + hash ^= AgentActions.GetHashCode(); + if (Command != 0) hash ^= Command.GetHashCode(); + if (SideChannel.Length != 0) hash ^= SideChannel.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + agentActions_.WriteTo(output, _map_agentActions_codec); + if (Command != 0) { + output.WriteRawTag(32); + output.WriteEnum((int) Command); + } + if (SideChannel.Length != 0) { + output.WriteRawTag(42); + output.WriteBytes(SideChannel); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + size += agentActions_.CalculateSize(_map_agentActions_codec); + if (Command != 0) { + size += 1 + pb::CodedOutputStream.ComputeEnumSize((int) Command); + } + if (SideChannel.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(SideChannel); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(UnityRLInputProto other) { + if (other == null) { + return; + } + agentActions_.Add(other.agentActions_); + if (other.Command != 0) { + Command = other.Command; + } + if (other.SideChannel.Length != 0) { + SideChannel = other.SideChannel; + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: { + agentActions_.AddEntriesFrom(input, _map_agentActions_codec); + break; + } + case 32: { + command_ = (global::Unity.MLAgents.CommunicatorObjects.CommandProto) input.ReadEnum(); + break; + } + case 42: { + SideChannel = input.ReadBytes(); + break; + } + } + } + } + + #region Nested types + /// Container for nested types declared in the UnityRLInputProto message type. + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static partial class Types { + internal sealed partial class ListAgentActionProto : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ListAgentActionProto()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Unity.MLAgents.CommunicatorObjects.UnityRLInputProto.Descriptor.NestedTypes[0]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public ListAgentActionProto() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public ListAgentActionProto(ListAgentActionProto other) : this() { + value_ = other.value_.Clone(); + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public ListAgentActionProto Clone() { + return new ListAgentActionProto(this); + } + + /// Field number for the "value" field. + public const int ValueFieldNumber = 1; + private static readonly pb::FieldCodec _repeated_value_codec + = pb::FieldCodec.ForMessage(10, global::Unity.MLAgents.CommunicatorObjects.AgentActionProto.Parser); + private readonly pbc::RepeatedField value_ = new pbc::RepeatedField(); + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public pbc::RepeatedField Value { + get { return value_; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as ListAgentActionProto); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(ListAgentActionProto other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if(!value_.Equals(other.value_)) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + hash ^= value_.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + value_.WriteTo(output, _repeated_value_codec); + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + size += value_.CalculateSize(_repeated_value_codec); + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(ListAgentActionProto other) { + if (other == null) { + return; + } + value_.Add(other.value_); + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: { + value_.AddEntriesFrom(input, _repeated_value_codec); + break; + } + } + } + } + + } + + } + #endregion + + } + + #endregion + +} + +#endregion Designer generated code diff --git a/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityRlInput.cs.meta b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityRlInput.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..48764a083a7a94e53d4b6c6c9fc5e2b7c7635b1f Binary files /dev/null and b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityRlInput.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityRlOutput.cs b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityRlOutput.cs new file mode 100644 index 0000000000000000000000000000000000000000..0971d50299328c405536886760e85dd0962838e0 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityRlOutput.cs @@ -0,0 +1,331 @@ +// +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: mlagents_envs/communicator_objects/unity_rl_output.proto +// +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = global::Google.Protobuf; +using pbc = global::Google.Protobuf.Collections; +using pbr = global::Google.Protobuf.Reflection; +using scg = global::System.Collections.Generic; +namespace Unity.MLAgents.CommunicatorObjects { + + /// Holder for reflection information generated from mlagents_envs/communicator_objects/unity_rl_output.proto + internal static partial class UnityRlOutputReflection { + + #region Descriptor + /// File descriptor for mlagents_envs/communicator_objects/unity_rl_output.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static UnityRlOutputReflection() { + byte[] descriptorData = global::System.Convert.FromBase64String( + string.Concat( + "CjhtbGFnZW50c19lbnZzL2NvbW11bmljYXRvcl9vYmplY3RzL3VuaXR5X3Js", + "X291dHB1dC5wcm90bxIUY29tbXVuaWNhdG9yX29iamVjdHMaM21sYWdlbnRz", + "X2VudnMvY29tbXVuaWNhdG9yX29iamVjdHMvYWdlbnRfaW5mby5wcm90byK5", + "AgoSVW5pdHlSTE91dHB1dFByb3RvEkwKCmFnZW50SW5mb3MYAiADKAsyOC5j", + "b21tdW5pY2F0b3Jfb2JqZWN0cy5Vbml0eVJMT3V0cHV0UHJvdG8uQWdlbnRJ", + "bmZvc0VudHJ5EhQKDHNpZGVfY2hhbm5lbBgDIAEoDBpJChJMaXN0QWdlbnRJ", + "bmZvUHJvdG8SMwoFdmFsdWUYASADKAsyJC5jb21tdW5pY2F0b3Jfb2JqZWN0", + "cy5BZ2VudEluZm9Qcm90bxpuCg9BZ2VudEluZm9zRW50cnkSCwoDa2V5GAEg", + "ASgJEkoKBXZhbHVlGAIgASgLMjsuY29tbXVuaWNhdG9yX29iamVjdHMuVW5p", + "dHlSTE91dHB1dFByb3RvLkxpc3RBZ2VudEluZm9Qcm90bzoCOAFKBAgBEAJC", + "JaoCIlVuaXR5Lk1MQWdlbnRzLkNvbW11bmljYXRvck9iamVjdHNiBnByb3Rv", + "Mw==")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { global::Unity.MLAgents.CommunicatorObjects.AgentInfoReflection.Descriptor, }, + new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(global::Unity.MLAgents.CommunicatorObjects.UnityRLOutputProto), global::Unity.MLAgents.CommunicatorObjects.UnityRLOutputProto.Parser, new[]{ "AgentInfos", "SideChannel" }, null, null, new pbr::GeneratedClrTypeInfo[] { new pbr::GeneratedClrTypeInfo(typeof(global::Unity.MLAgents.CommunicatorObjects.UnityRLOutputProto.Types.ListAgentInfoProto), global::Unity.MLAgents.CommunicatorObjects.UnityRLOutputProto.Types.ListAgentInfoProto.Parser, new[]{ "Value" }, null, null, null), + null, }) + })); + } + #endregion + + } + #region Messages + internal sealed partial class UnityRLOutputProto : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new UnityRLOutputProto()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Unity.MLAgents.CommunicatorObjects.UnityRlOutputReflection.Descriptor.MessageTypes[0]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public UnityRLOutputProto() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public UnityRLOutputProto(UnityRLOutputProto other) : this() { + agentInfos_ = other.agentInfos_.Clone(); + sideChannel_ = other.sideChannel_; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public UnityRLOutputProto Clone() { + return new UnityRLOutputProto(this); + } + + /// Field number for the "agentInfos" field. + public const int AgentInfosFieldNumber = 2; + private static readonly pbc::MapField.Codec _map_agentInfos_codec + = new pbc::MapField.Codec(pb::FieldCodec.ForString(10), pb::FieldCodec.ForMessage(18, global::Unity.MLAgents.CommunicatorObjects.UnityRLOutputProto.Types.ListAgentInfoProto.Parser), 18); + private readonly pbc::MapField agentInfos_ = new pbc::MapField(); + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public pbc::MapField AgentInfos { + get { return agentInfos_; } + } + + /// Field number for the "side_channel" field. + public const int SideChannelFieldNumber = 3; + private pb::ByteString sideChannel_ = pb::ByteString.Empty; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public pb::ByteString SideChannel { + get { return sideChannel_; } + set { + sideChannel_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as UnityRLOutputProto); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(UnityRLOutputProto other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!AgentInfos.Equals(other.AgentInfos)) return false; + if (SideChannel != other.SideChannel) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + hash ^= AgentInfos.GetHashCode(); + if (SideChannel.Length != 0) hash ^= SideChannel.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + agentInfos_.WriteTo(output, _map_agentInfos_codec); + if (SideChannel.Length != 0) { + output.WriteRawTag(26); + output.WriteBytes(SideChannel); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + size += agentInfos_.CalculateSize(_map_agentInfos_codec); + if (SideChannel.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(SideChannel); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(UnityRLOutputProto other) { + if (other == null) { + return; + } + agentInfos_.Add(other.agentInfos_); + if (other.SideChannel.Length != 0) { + SideChannel = other.SideChannel; + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 18: { + agentInfos_.AddEntriesFrom(input, _map_agentInfos_codec); + break; + } + case 26: { + SideChannel = input.ReadBytes(); + break; + } + } + } + } + + #region Nested types + /// Container for nested types declared in the UnityRLOutputProto message type. + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static partial class Types { + internal sealed partial class ListAgentInfoProto : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ListAgentInfoProto()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Unity.MLAgents.CommunicatorObjects.UnityRLOutputProto.Descriptor.NestedTypes[0]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public ListAgentInfoProto() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public ListAgentInfoProto(ListAgentInfoProto other) : this() { + value_ = other.value_.Clone(); + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public ListAgentInfoProto Clone() { + return new ListAgentInfoProto(this); + } + + /// Field number for the "value" field. + public const int ValueFieldNumber = 1; + private static readonly pb::FieldCodec _repeated_value_codec + = pb::FieldCodec.ForMessage(10, global::Unity.MLAgents.CommunicatorObjects.AgentInfoProto.Parser); + private readonly pbc::RepeatedField value_ = new pbc::RepeatedField(); + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public pbc::RepeatedField Value { + get { return value_; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as ListAgentInfoProto); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(ListAgentInfoProto other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if(!value_.Equals(other.value_)) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + hash ^= value_.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + value_.WriteTo(output, _repeated_value_codec); + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + size += value_.CalculateSize(_repeated_value_codec); + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(ListAgentInfoProto other) { + if (other == null) { + return; + } + value_.Add(other.value_); + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: { + value_.AddEntriesFrom(input, _repeated_value_codec); + break; + } + } + } + } + + } + + } + #endregion + + } + + #endregion + +} + +#endregion Designer generated code diff --git a/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityRlOutput.cs.meta b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityRlOutput.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..132cdb7d016e01b0b370c2bd9b33514ef51f1a7c Binary files /dev/null and b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityRlOutput.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityToExternal.cs b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityToExternal.cs new file mode 100644 index 0000000000000000000000000000000000000000..ddb47dea96b93a482dae01c0717295ef8387f6bb --- /dev/null +++ b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityToExternal.cs @@ -0,0 +1,43 @@ +// +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: mlagents_envs/communicator_objects/unity_to_external.proto +// +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = global::Google.Protobuf; +using pbc = global::Google.Protobuf.Collections; +using pbr = global::Google.Protobuf.Reflection; +using scg = global::System.Collections.Generic; +namespace Unity.MLAgents.CommunicatorObjects { + + /// Holder for reflection information generated from mlagents_envs/communicator_objects/unity_to_external.proto + public static partial class UnityToExternalReflection { + + #region Descriptor + /// File descriptor for mlagents_envs/communicator_objects/unity_to_external.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static UnityToExternalReflection() { + byte[] descriptorData = global::System.Convert.FromBase64String( + string.Concat( + "CjptbGFnZW50c19lbnZzL2NvbW11bmljYXRvcl9vYmplY3RzL3VuaXR5X3Rv", + "X2V4dGVybmFsLnByb3RvEhRjb21tdW5pY2F0b3Jfb2JqZWN0cxo2bWxhZ2Vu", + "dHNfZW52cy9jb21tdW5pY2F0b3Jfb2JqZWN0cy91bml0eV9tZXNzYWdlLnBy", + "b3RvMnYKFFVuaXR5VG9FeHRlcm5hbFByb3RvEl4KCEV4Y2hhbmdlEicuY29t", + "bXVuaWNhdG9yX29iamVjdHMuVW5pdHlNZXNzYWdlUHJvdG8aJy5jb21tdW5p", + "Y2F0b3Jfb2JqZWN0cy5Vbml0eU1lc3NhZ2VQcm90byIAQiWqAiJVbml0eS5N", + "TEFnZW50cy5Db21tdW5pY2F0b3JPYmplY3RzYgZwcm90bzM=")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { global::Unity.MLAgents.CommunicatorObjects.UnityMessageReflection.Descriptor, }, + new pbr::GeneratedClrTypeInfo(null, null)); + } + #endregion + + } +} + +#endregion Designer generated code diff --git a/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityToExternal.cs.meta b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityToExternal.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..50c400f34532ca127774532f67f9e5788b94447e Binary files /dev/null and b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityToExternal.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityToExternalGrpc.cs b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityToExternalGrpc.cs new file mode 100644 index 0000000000000000000000000000000000000000..273c57cb1d1fab4c2db8cb84a70d8e420f7a233e --- /dev/null +++ b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityToExternalGrpc.cs @@ -0,0 +1,135 @@ +#if UNITY_EDITOR || UNITY_STANDALONE +#define MLA_SUPPORTED_TRAINING_PLATFORM +#endif +#if MLA_SUPPORTED_TRAINING_PLATFORM +// +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: mlagents_envs/communicator_objects/unity_to_external.proto +// +#pragma warning disable 0414, 1591 +#region Designer generated code + +using grpc = global::Grpc.Core; + +namespace Unity.MLAgents.CommunicatorObjects { + internal static partial class UnityToExternalProto + { + static readonly string __ServiceName = "communicator_objects.UnityToExternalProto"; + + static readonly grpc::Marshaller __Marshaller_communicator_objects_UnityMessageProto = grpc::Marshallers.Create((arg) => global::Google.Protobuf.MessageExtensions.ToByteArray(arg), global::Unity.MLAgents.CommunicatorObjects.UnityMessageProto.Parser.ParseFrom); + + static readonly grpc::Method __Method_Exchange = new grpc::Method( + grpc::MethodType.Unary, + __ServiceName, + "Exchange", + __Marshaller_communicator_objects_UnityMessageProto, + __Marshaller_communicator_objects_UnityMessageProto); + + /// Service descriptor + public static global::Google.Protobuf.Reflection.ServiceDescriptor Descriptor + { + get { return global::Unity.MLAgents.CommunicatorObjects.UnityToExternalReflection.Descriptor.Services[0]; } + } + + /// Base class for server-side implementations of UnityToExternalProto + public abstract partial class UnityToExternalProtoBase + { + /// + /// Sends the academy parameters + /// + /// The request received from the client. + /// The context of the server-side call handler being invoked. + /// The response to send back to the client (wrapped by a task). + public virtual global::System.Threading.Tasks.Task Exchange(global::Unity.MLAgents.CommunicatorObjects.UnityMessageProto request, grpc::ServerCallContext context) + { + throw new grpc::RpcException(new grpc::Status(grpc::StatusCode.Unimplemented, "")); + } + + } + + /// Client for UnityToExternalProto + public partial class UnityToExternalProtoClient : grpc::ClientBase + { + /// Creates a new client for UnityToExternalProto + /// The channel to use to make remote calls. + public UnityToExternalProtoClient(grpc::Channel channel) : base(channel) + { + } + /// Creates a new client for UnityToExternalProto that uses a custom CallInvoker. + /// The callInvoker to use to make remote calls. + public UnityToExternalProtoClient(grpc::CallInvoker callInvoker) : base(callInvoker) + { + } + /// Protected parameterless constructor to allow creation of test doubles. + protected UnityToExternalProtoClient() : base() + { + } + /// Protected constructor to allow creation of configured clients. + /// The client configuration. + protected UnityToExternalProtoClient(ClientBaseConfiguration configuration) : base(configuration) + { + } + + /// + /// Sends the academy parameters + /// + /// The request to send to the server. + /// The initial metadata to send with the call. This parameter is optional. + /// An optional deadline for the call. The call will be cancelled if deadline is hit. + /// An optional token for canceling the call. + /// The response received from the server. + public virtual global::Unity.MLAgents.CommunicatorObjects.UnityMessageProto Exchange(global::Unity.MLAgents.CommunicatorObjects.UnityMessageProto request, grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) + { + return Exchange(request, new grpc::CallOptions(headers, deadline, cancellationToken)); + } + /// + /// Sends the academy parameters + /// + /// The request to send to the server. + /// The options for the call. + /// The response received from the server. + public virtual global::Unity.MLAgents.CommunicatorObjects.UnityMessageProto Exchange(global::Unity.MLAgents.CommunicatorObjects.UnityMessageProto request, grpc::CallOptions options) + { + return CallInvoker.BlockingUnaryCall(__Method_Exchange, null, options, request); + } + /// + /// Sends the academy parameters + /// + /// The request to send to the server. + /// The initial metadata to send with the call. This parameter is optional. + /// An optional deadline for the call. The call will be cancelled if deadline is hit. + /// An optional token for canceling the call. + /// The call object. + public virtual grpc::AsyncUnaryCall ExchangeAsync(global::Unity.MLAgents.CommunicatorObjects.UnityMessageProto request, grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) + { + return ExchangeAsync(request, new grpc::CallOptions(headers, deadline, cancellationToken)); + } + /// + /// Sends the academy parameters + /// + /// The request to send to the server. + /// The options for the call. + /// The call object. + public virtual grpc::AsyncUnaryCall ExchangeAsync(global::Unity.MLAgents.CommunicatorObjects.UnityMessageProto request, grpc::CallOptions options) + { + return CallInvoker.AsyncUnaryCall(__Method_Exchange, null, options, request); + } + /// Creates a new instance of client from given ClientBaseConfiguration. + protected override UnityToExternalProtoClient NewInstance(ClientBaseConfiguration configuration) + { + return new UnityToExternalProtoClient(configuration); + } + } + + /// Creates service definition that can be registered with a server + /// An object implementing the server-side handling logic. + public static grpc::ServerServiceDefinition BindService(UnityToExternalProtoBase serviceImpl) + { + return grpc::ServerServiceDefinition.CreateBuilder() + .AddMethod(__Method_Exchange, serviceImpl.Exchange).Build(); + } + + } +} +#endregion +#endif diff --git a/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityToExternalGrpc.cs.meta b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityToExternalGrpc.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..be77e859a8240da89f748adcc201284a5648d6a8 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Grpc/CommunicatorObjects/UnityToExternalGrpc.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Grpc/Unity.ML-Agents.CommunicatorObjects.asmdef b/com.unity.ml-agents/Runtime/Grpc/Unity.ML-Agents.CommunicatorObjects.asmdef new file mode 100644 index 0000000000000000000000000000000000000000..6c62ff7bbecaef0445b9ca933e3bb830f6dfdaf3 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Grpc/Unity.ML-Agents.CommunicatorObjects.asmdef @@ -0,0 +1,18 @@ +{ + "name": "Unity.ML-Agents.CommunicatorObjects", + "rootNamespace": "", + "references": [], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": true, + "precompiledReferences": [ + "System.IO.Abstractions.dll", + "Grpc.Core.dll", + "Google.Protobuf_Packed.dll" + ], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/com.unity.ml-agents/Runtime/Grpc/Unity.ML-Agents.CommunicatorObjects.asmdef.meta b/com.unity.ml-agents/Runtime/Grpc/Unity.ML-Agents.CommunicatorObjects.asmdef.meta new file mode 100644 index 0000000000000000000000000000000000000000..1bf58ee5ea5ce2f3833ea6b2465083633450b567 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Grpc/Unity.ML-Agents.CommunicatorObjects.asmdef.meta differ diff --git a/com.unity.ml-agents/Runtime/IMultiAgentGroup.cs b/com.unity.ml-agents/Runtime/IMultiAgentGroup.cs new file mode 100644 index 0000000000000000000000000000000000000000..0dcdd0a5afeba794feb5c37c144a68a0b0168f4f --- /dev/null +++ b/com.unity.ml-agents/Runtime/IMultiAgentGroup.cs @@ -0,0 +1,28 @@ +namespace Unity.MLAgents +{ + /// + /// MultiAgentGroup interface for grouping agents to support multi-agent training. + /// + public interface IMultiAgentGroup + { + /// + /// Get the ID of MultiAgentGroup. + /// + /// + /// MultiAgentGroup ID. + /// + int GetId(); + + /// + /// Register agent to the MultiAgentGroup. + /// + /// The Agent to register. + void RegisterAgent(Agent agent); + + /// + /// Unregister agent from the MultiAgentGroup. + /// + /// The Agent to unregister. + void UnregisterAgent(Agent agent); + } +} diff --git a/com.unity.ml-agents/Runtime/IMultiAgentGroup.cs.meta b/com.unity.ml-agents/Runtime/IMultiAgentGroup.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..b9171ab0408bbbb6e18ebf5c360caba9dccff25e Binary files /dev/null and b/com.unity.ml-agents/Runtime/IMultiAgentGroup.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Inference.meta b/com.unity.ml-agents/Runtime/Inference.meta new file mode 100644 index 0000000000000000000000000000000000000000..cb7450c10e8be7c090ee9ff1db0f0b7092a34067 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Inference.meta differ diff --git a/com.unity.ml-agents/Runtime/Inference/ApplierImpl.cs b/com.unity.ml-agents/Runtime/Inference/ApplierImpl.cs new file mode 100644 index 0000000000000000000000000000000000000000..f7f137cd5084ea64ceabad7c8dc6a1991f2c268d --- /dev/null +++ b/com.unity.ml-agents/Runtime/Inference/ApplierImpl.cs @@ -0,0 +1,228 @@ +using System.Collections.Generic; +using System.Linq; +using Unity.MLAgents.Inference.Utils; +using Unity.MLAgents.Actuators; +using Unity.InferenceEngine; +using UnityEngine; + +namespace Unity.MLAgents.Inference +{ + /// + /// The Applier for the Continuous Action output tensor. Tensor is assumed to contain the + /// continuous action data of the agents in the batch. + /// + internal class ContinuousActionOutputApplier : TensorApplier.IApplier + { + readonly ActionSpec m_ActionSpec; + + public ContinuousActionOutputApplier(ActionSpec actionSpec) + { + m_ActionSpec = actionSpec; + } + + public void Apply(TensorProxy tensorProxy, IList actionIds, Dictionary lastActions) + { + var actionSize = tensorProxy.shape[^1]; + + tensorProxy.data.CompleteAllPendingOperations(); + + var agentIndex = 0; + + for (var i = 0; i < actionIds.Count; i++) + { + var agentId = actionIds[i]; + if (lastActions.ContainsKey(agentId)) + { + var actionBuffer = lastActions[agentId]; + if (actionBuffer.IsEmpty()) + { + actionBuffer = new ActionBuffers(m_ActionSpec); + lastActions[agentId] = actionBuffer; + } + + var continuousBuffer = actionBuffer.ContinuousActions; + + for (var j = 0; j < actionSize; j++) + { + continuousBuffer[j] = ((Tensor)tensorProxy.data)[agentIndex, j]; + } + } + + agentIndex++; + } + } + } + + /// + /// The Applier for the Discrete Action output tensor. + /// + internal class DiscreteActionOutputApplier : TensorApplier.IApplier + { + readonly ActionSpec m_ActionSpec; + + public DiscreteActionOutputApplier(ActionSpec actionSpec, int seed) + { + m_ActionSpec = actionSpec; + } + + public void Apply(TensorProxy tensorProxy, IList actionIds, Dictionary lastActions) + { + var agentIndex = 0; + + tensorProxy.data.CompleteAllPendingOperations(); + + var actionSize = tensorProxy.shape[tensorProxy.shape.Length - 1]; + + for (var i = 0; i < actionIds.Count; i++) + { + var agentId = actionIds[i]; + if (lastActions.ContainsKey(agentId)) + { + var actionBuffer = lastActions[agentId]; + if (actionBuffer.IsEmpty()) + { + actionBuffer = new ActionBuffers(m_ActionSpec); + lastActions[agentId] = actionBuffer; + } + + var discreteBuffer = actionBuffer.DiscreteActions; + + for (var j = 0; j < actionSize; j++) + { + discreteBuffer[j] = ((Tensor)tensorProxy.data)[agentIndex, j]; + } + } + + agentIndex++; + } + } + } + + /// + /// The Applier for the Discrete Action output tensor. Uses multinomial to sample discrete + /// actions from the logits contained in the tensor. + /// + internal class LegacyDiscreteActionOutputApplier : TensorApplier.IApplier + { + readonly int[] m_ActionSize; + readonly Multinomial m_Multinomial; + readonly ActionSpec m_ActionSpec; + readonly int[] m_StartActionIndices; + readonly float[] m_CdfBuffer; + + public LegacyDiscreteActionOutputApplier(ActionSpec actionSpec, int seed) + { + m_ActionSize = actionSpec.BranchSizes; + m_Multinomial = new Multinomial(seed); + m_ActionSpec = actionSpec; + m_StartActionIndices = Utilities.CumSum(m_ActionSize); + + // Scratch space for computing the cumulative distribution function. + // In order to reuse it, make it the size of the largest branch. + var largestBranch = Mathf.Max(m_ActionSize); + m_CdfBuffer = new float[largestBranch]; + } + + public void Apply(TensorProxy tensorProxy, IList actionIds, Dictionary lastActions) + { + var agentIndex = 0; + for (var i = 0; i < actionIds.Count; i++) + { + var agentId = actionIds[i]; + if (lastActions.ContainsKey(agentId)) + { + var actionBuffer = lastActions[agentId]; + if (actionBuffer.IsEmpty()) + { + actionBuffer = new ActionBuffers(m_ActionSpec); + lastActions[agentId] = actionBuffer; + } + + var discreteBuffer = actionBuffer.DiscreteActions; + for (var j = 0; j < m_ActionSize.Length; j++) + { + ComputeCdf(tensorProxy, agentIndex, m_StartActionIndices[j], m_ActionSize[j]); + discreteBuffer[j] = m_Multinomial.Sample(m_CdfBuffer, m_ActionSize[j]); + } + } + + agentIndex++; + } + } + + /// + /// Compute the cumulative distribution function for a given agent's action + /// given the log-probabilities. + /// The results are stored in m_CdfBuffer, which is the size of the largest action's number of branches. + /// + /// + /// Index of the agent being considered + /// Offset into the tensor's channel. + /// + internal void ComputeCdf(TensorProxy logProbs, int batch, int channelOffset, int branchSize) + { + // Find the class maximum + var maxProb = float.NegativeInfinity; + + logProbs.data.CompleteAllPendingOperations(); + + for (var cls = 0; cls < branchSize; ++cls) + { + maxProb = Mathf.Max(((Tensor)logProbs.data)[batch, cls + channelOffset], maxProb); + } + + // Sum the log probabilities and compute CDF + var sumProb = 0.0f; + + for (var cls = 0; cls < branchSize; ++cls) + { + sumProb += Mathf.Exp(((Tensor)logProbs.data)[batch, cls + channelOffset] - maxProb); + m_CdfBuffer[cls] = sumProb; + } + } + } + + /// + /// The Applier for the Memory output tensor. Tensor is assumed to contain the new + /// memory data of the agents in the batch. + /// + internal class MemoryOutputApplier : TensorApplier.IApplier + { + Dictionary> m_Memories; + + public MemoryOutputApplier( + Dictionary> memories) + { + m_Memories = memories; + } + + public void Apply(TensorProxy tensorProxy, IList actionIds, Dictionary lastActions) + { + var agentIndex = 0; + + tensorProxy.data.CompleteAllPendingOperations(); + + var memorySize = tensorProxy.data.Width(); + + for (var i = 0; i < actionIds.Count; i++) + { + var agentId = actionIds[i]; + List memory; + if (!m_Memories.TryGetValue(agentId, out memory) + || memory.Count < memorySize) + { + memory = new List(); + memory.AddRange(Enumerable.Repeat(0f, memorySize)); + } + + for (var j = 0; j < memorySize; j++) + { + memory[j] = ((Tensor)tensorProxy.data)[agentIndex, 0, j]; + } + + m_Memories[agentId] = memory; + agentIndex++; + } + } + } +} diff --git a/com.unity.ml-agents/Runtime/Inference/ApplierImpl.cs.meta b/com.unity.ml-agents/Runtime/Inference/ApplierImpl.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..b6ecb20fa60aa1b8c40cdb7fafd3eaf15902b1eb Binary files /dev/null and b/com.unity.ml-agents/Runtime/Inference/ApplierImpl.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Inference/DynamicTensorShapeExtensions.cs b/com.unity.ml-agents/Runtime/Inference/DynamicTensorShapeExtensions.cs new file mode 100644 index 0000000000000000000000000000000000000000..5a07412701c2cabc62be30db2e9cc27c491f41a8 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Inference/DynamicTensorShapeExtensions.cs @@ -0,0 +1,33 @@ +using Unity.InferenceEngine; +using UnityEngine.Assertions; + +namespace Unity.MLAgents.Inference +{ + static class DynamicTensorShapeExtensions + { + public static int[] ToArray(this DynamicTensorShape shape) + { + var shapeOut = new int[shape.rank]; + + // TODO investigate how critical this is and if we can just remove this assert. the alternative is to expose this again in Sentis. + + // Assert.IsTrue(shape.hasRank, "ValueError: Cannot convert tensor of unknown rank to TensorShape"); + + var shapeArray = shape.ToIntArray(); + + for (var i = 0; i < shape.rank; i++) + { + if (shapeArray[i] == -1) + { + shapeOut[i] = 1; + } + else + { + shapeOut[i] = shapeArray[i]; + } + } + + return shapeOut; + } + } +} diff --git a/com.unity.ml-agents/Runtime/Inference/DynamicTensorShapeExtensions.cs.meta b/com.unity.ml-agents/Runtime/Inference/DynamicTensorShapeExtensions.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..59b194281acb9a463abccbabe715358eb7738404 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Inference/DynamicTensorShapeExtensions.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Inference/GeneratorImpl.cs b/com.unity.ml-agents/Runtime/Inference/GeneratorImpl.cs new file mode 100644 index 0000000000000000000000000000000000000000..028161a1256194873d17092fccf1ce140d995baf --- /dev/null +++ b/com.unity.ml-agents/Runtime/Inference/GeneratorImpl.cs @@ -0,0 +1,279 @@ +using System.Collections.Generic; +using System; +using Unity.InferenceEngine; +using Unity.MLAgents.Inference.Utils; +using Unity.MLAgents.Sensors; +using static Unity.MLAgents.Inference.TensorProxy; + +namespace Unity.MLAgents.Inference +{ + /// + /// Reshapes a Tensor so that its first dimension becomes equal to the current batch size + /// and initializes its content to be zeros. Will only work on 2-dimensional tensors. + /// The second dimension of the Tensor will not be modified. + /// + internal class BiDimensionalOutputGenerator : TensorGenerator.IGenerator + { + public BiDimensionalOutputGenerator() { } + + public void Generate(TensorProxy tensorProxy, int batchSize, IList infos) + { + TensorUtils.ResizeTensor(tensorProxy, batchSize); + } + } + + /// + /// Generates the Tensor corresponding to the BatchSize input : Will be a one dimensional + /// integer array of size 1 containing the batch size. + /// + internal class BatchSizeGenerator : TensorGenerator.IGenerator + { + public BatchSizeGenerator() { } + + public void Generate(TensorProxy tensorProxy, int batchSize, IList infos) + { + tensorProxy.data?.Dispose(); + var newTensorShape = new TensorShape(1, 1); + tensorProxy.data = TensorUtils.CreateEmptyTensor(newTensorShape, tensorProxy.DType); + tensorProxy.data.CompleteAllPendingOperations(); + + ((Tensor)tensorProxy.data)[0] = batchSize; + } + } + + /// + /// Generates the Tensor corresponding to the SequenceLength input : Will be a one + /// dimensional integer array of size 1 containing 1. + /// Note : the sequence length is always one since recurrent networks only predict for + /// one step at the time. + /// + internal class SequenceLengthGenerator : TensorGenerator.IGenerator + { + public SequenceLengthGenerator() { } + + public void Generate(TensorProxy tensorProxy, int batchSize, IList infos) + { + tensorProxy.shape = Array.Empty(); + tensorProxy.data?.Dispose(); + var newTensorShape = new TensorShape(1, 1); + tensorProxy.data = TensorUtils.CreateEmptyTensor(newTensorShape, tensorProxy.DType); + tensorProxy.data.CompleteAllPendingOperations(); + + ((Tensor)tensorProxy.data)[0] = 1; + } + } + + /// + /// Generates the Tensor corresponding to the Recurrent input : Will be a two + /// dimensional float array of dimension [batchSize x memorySize]. + /// It will use the Memory data contained in the agentInfo to fill the data + /// of the tensor. + /// + internal class RecurrentInputGenerator : TensorGenerator.IGenerator + { + Dictionary> m_Memories; + + public RecurrentInputGenerator( + Dictionary> memories) + { + m_Memories = memories; + } + + public void Generate( + TensorProxy tensorProxy, int batchSize, IList infos) + { + TensorUtils.ResizeTensor(tensorProxy, batchSize); + + var memorySize = tensorProxy.data.Width(); + + tensorProxy.data.CompleteAllPendingOperations(); + + var agentIndex = 0; + + for (var infoIndex = 0; infoIndex < infos.Count; infoIndex++) + { + var infoSensorPair = infos[infoIndex]; + var info = infoSensorPair.agentInfo; + List memory; + + if (info.done) + { + m_Memories.Remove(info.episodeId); + } + + if (!m_Memories.TryGetValue(info.episodeId, out memory)) + { + + for (var j = 0; j < memorySize; j++) + { + ((Tensor)tensorProxy.data)[agentIndex, 0, j] = 0; + } + + agentIndex++; + continue; + } + + for (var j = 0; j < Math.Min(memorySize, memory.Count); j++) + { + if (j >= memory.Count) + { + break; + } + + ((Tensor)tensorProxy.data)[agentIndex, 0, j] = memory[j]; + } + + agentIndex++; + } + } + } + + /// + /// Generates the Tensor corresponding to the Previous Action input : Will be a two + /// dimensional integer array of dimension [batchSize x actionSize]. + /// It will use the previous action data contained in the agentInfo to fill the data + /// of the tensor. + /// + internal class PreviousActionInputGenerator : TensorGenerator.IGenerator + { + public PreviousActionInputGenerator() { } + + public void Generate(TensorProxy tensorProxy, int batchSize, IList infos) + { + TensorUtils.ResizeTensor(tensorProxy, batchSize); + tensorProxy.data.CompleteAllPendingOperations(); + + var actionSize = tensorProxy.shape[tensorProxy.shape.Length - 1]; + var agentIndex = 0; + for (var infoIndex = 0; infoIndex < infos.Count; infoIndex++) + { + var infoSensorPair = infos[infoIndex]; + var info = infoSensorPair.agentInfo; + var pastAction = info.storedActions.DiscreteActions; + if (!pastAction.IsEmpty()) + { + for (var j = 0; j < actionSize; j++) + { + ((Tensor)tensorProxy.data)[agentIndex, j] = pastAction[j]; + } + } + + agentIndex++; + } + } + } + + /// + /// Generates the Tensor corresponding to the Action Mask input : Will be a two + /// dimensional float array of dimension [batchSize x numActionLogits]. + /// It will use the Action Mask data contained in the agentInfo to fill the data + /// of the tensor. + /// + internal class ActionMaskInputGenerator : TensorGenerator.IGenerator + { + public ActionMaskInputGenerator() { } + + public void Generate(TensorProxy tensorProxy, int batchSize, IList infos) + { + TensorUtils.ResizeTensor(tensorProxy, batchSize); + + tensorProxy.data.CompleteAllPendingOperations(); + + var maskSize = tensorProxy.shape[tensorProxy.shape.Length - 1]; + var agentIndex = 0; + for (var infoIndex = 0; infoIndex < infos.Count; infoIndex++) + { + var infoSensorPair = infos[infoIndex]; + var agentInfo = infoSensorPair.agentInfo; + var maskList = agentInfo.discreteActionMasks; + + for (var j = 0; j < maskSize; j++) + { + var isUnmasked = (maskList != null && maskList[j]) ? 0.0f : 1.0f; + ((Tensor)tensorProxy.data)[agentIndex, j] = isUnmasked; + } + + agentIndex++; + } + } + } + + /// + /// Generates the Tensor corresponding to the Epsilon input : Will be a two + /// dimensional float array of dimension [batchSize x actionSize]. + /// It will use the generate random input data from a normal Distribution. + /// + internal class RandomNormalInputGenerator : TensorGenerator.IGenerator + { + readonly RandomNormal m_RandomNormal; + + public RandomNormalInputGenerator(int seed) + { + m_RandomNormal = new RandomNormal(seed); + } + + public void Generate(TensorProxy tensorProxy, int batchSize, IList infos) + { + TensorUtils.ResizeTensor(tensorProxy, batchSize); + TensorUtils.FillTensorWithRandomNormal(tensorProxy, m_RandomNormal); + } + } + + /// + /// Generates the Tensor corresponding to the Observation input : Will be a multi + /// dimensional float array. + /// It will use the Observation data contained in the sensors to fill the data + /// of the tensor. + /// + internal class ObservationGenerator : TensorGenerator.IGenerator + { + List m_SensorIndices = new List(); + ObservationWriter m_ObservationWriter = new ObservationWriter(); + + public ObservationGenerator() { } + + public void AddSensorIndex(int sensorIndex) + { + m_SensorIndices.Add(sensorIndex); + } + + public void Generate(TensorProxy tensorProxy, int batchSize, IList infos) + { + TensorUtils.ResizeTensor(tensorProxy, batchSize); + var agentIndex = 0; + for (var infoIndex = 0; infoIndex < infos.Count; infoIndex++) + { + var info = infos[infoIndex]; + if (info.agentInfo.done) + { + // If the agent is done, we might have a stale reference to the sensors + // e.g. a dependent object might have been disposed. + // To avoid this, just fill observation with zeroes instead of calling sensor.Write. + TensorUtils.FillTensorBatch(tensorProxy, agentIndex, 0.0f); + } + else + { + var tensorOffset = 0; + var tensorCapacity = tensorProxy.data.shape.rank >= 2 ? tensorProxy.data.shape[1] : 0; + + for (var sensorIndexIndex = 0; sensorIndexIndex < m_SensorIndices.Count; sensorIndexIndex++) + { + if (tensorOffset >= tensorCapacity) + { + UnityEngine.Debug.LogWarning($"[ml-agents] Sensor write overflow: tensorOffset ({tensorOffset}) reached tensor capacity ({tensorCapacity}). Skipping remaining sensors to prevent buffer overrun."); + break; + } + + var sensorIndex = m_SensorIndices[sensorIndexIndex]; + var sensor = info.sensors[sensorIndex]; + m_ObservationWriter.SetTarget(tensorProxy, agentIndex, tensorOffset); + var numWritten = sensor.Write(m_ObservationWriter); + tensorOffset += numWritten; + } + } + + agentIndex++; + } + } + } +} diff --git a/com.unity.ml-agents/Runtime/Inference/GeneratorImpl.cs.meta b/com.unity.ml-agents/Runtime/Inference/GeneratorImpl.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..1f628e0e51c5f8aacf99529c0bddc6a4267d34a7 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Inference/GeneratorImpl.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Inference/ModelRunner.cs b/com.unity.ml-agents/Runtime/Inference/ModelRunner.cs new file mode 100644 index 0000000000000000000000000000000000000000..751d730e4e58ed75bc3e606ccd3adea42ba29007 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Inference/ModelRunner.cs @@ -0,0 +1,267 @@ +using System.Collections.Generic; +using Unity.InferenceEngine; +using UnityEngine.Profiling; +using Unity.MLAgents.Actuators; +using Unity.MLAgents.Policies; +using Unity.MLAgents.Sensors; + +namespace Unity.MLAgents.Inference +{ + internal struct AgentInfoSensorsPair + { + public AgentInfo agentInfo; + public List sensors; + } + + internal class ModelRunner + { + List m_Infos = new List(); + Dictionary m_LastActionsReceived = new Dictionary(); + List m_OrderedAgentsRequestingDecisions = new List(); + + TensorGenerator m_TensorGenerator; + TensorApplier m_TensorApplier; + + ModelAsset m_Model; + string m_ModelName; + InferenceDevice m_InferenceDevice; + Worker m_Engine; + bool m_DeterministicInference; + string[] m_OutputNames; + IReadOnlyList m_InferenceInputs; + List m_InferenceOutputs; + Dictionary m_InputsByName; + Dictionary> m_Memories = new Dictionary>(); + + SensorShapeValidator m_SensorShapeValidator = new SensorShapeValidator(); + + bool m_ObservationsInitialized; + + /// + /// Initializes the Brain with the Model that it will use when selecting actions for + /// the agents + /// + /// The Sentis model to load + /// Description of the actions for the Agent. + /// Inference execution device. CPU is the fastest + /// option for most of ML Agents models. + /// The seed that will be used to initialize the RandomNormal + /// and Multinomial objects used when running inference. + /// Inference only: set to true if the action selection from model should be + /// deterministic. + /// Throws an error when the model is null + /// + public ModelRunner( + ModelAsset model, + ActionSpec actionSpec, + InferenceDevice inferenceDevice, + int seed = 0, + bool deterministicInference = false) + { + Model sentisModel; + SentisModelInfo sentisModelInfo; + m_Model = model; + m_ModelName = model?.name; + m_InferenceDevice = inferenceDevice; + m_DeterministicInference = deterministicInference; + if (model != null) + { +#if SENTIS_VERBOSE + m_Verbose = true; +#endif + + // TODO check w/Alex about verbosity level + // D.logEnabled = m_Verbose; + + sentisModel = ModelLoader.Load(model); + sentisModelInfo = new SentisModelInfo(sentisModel, deterministicInference); + + var failedCheck = SentisModelParamLoader.CheckModelVersion( + sentisModelInfo + ); + if (failedCheck != null) + { + if (failedCheck.CheckType == SentisModelParamLoader.FailedCheck.CheckTypeEnum.Error) + { + throw new UnityAgentsException(failedCheck.Message); + } + } + + BackendType executionDevice; + // WorkerFactory.Type executionDevice; + switch (inferenceDevice) + { + case InferenceDevice.ComputeShader: + executionDevice = BackendType.GPUCompute; + break; + case InferenceDevice.PixelShader: + executionDevice = BackendType.GPUPixel; + break; + case InferenceDevice.Burst: + executionDevice = BackendType.CPU; + break; + case InferenceDevice.Default: // fallthrough + default: + executionDevice = BackendType.CPU; + break; + } + m_Engine = new Worker(sentisModel, executionDevice); + } + else + { + sentisModel = null; + sentisModelInfo = null; + m_Engine = null; + } + + if (sentisModelInfo != null) + { + m_InferenceInputs = sentisModelInfo.GetInputTensors(); + m_OutputNames = sentisModelInfo.OutputNames; + } + + m_TensorGenerator = new TensorGenerator( + seed, m_Memories, sentisModel, m_DeterministicInference); + m_TensorApplier = new TensorApplier( + actionSpec, seed, m_Memories, sentisModel, m_DeterministicInference); + m_InputsByName = new Dictionary(); + m_InferenceOutputs = new List(); + sentisModelInfo?.Dispose(); + } + + public InferenceDevice InferenceDevice + { + get { return m_InferenceDevice; } + } + + public ModelAsset Model + { + get { return m_Model; } + } + + void PrepareSentisInputs(IReadOnlyList infInputs) + { + m_InputsByName.Clear(); + for (var i = 0; i < infInputs.Count; i++) + { + var inp = infInputs[i]; + m_InputsByName[inp.name] = inp.data; + } + } + + public void Dispose() + { + if (m_Engine != null) + m_Engine.Dispose(); + foreach (var (name, tensor) in m_InputsByName) + { + tensor.Dispose(); + } + } + + void FetchSentisOutputs(string[] names) + { + m_InferenceOutputs.Clear(); + + foreach (var n in names) + { + var output = m_Engine.PeekOutput(n); + m_InferenceOutputs.Add(TensorUtils.TensorProxyFromSentis(output, n)); + } + } + + public void PutObservations(AgentInfo info, List sensors) + { +#if DEBUG + m_SensorShapeValidator.ValidateSensors(sensors); +#endif + m_Infos.Add(new AgentInfoSensorsPair + { + agentInfo = info, + sensors = sensors + }); + + // We add the episodeId to this list to maintain the order in which the decisions were requested + m_OrderedAgentsRequestingDecisions.Add(info.episodeId); + + if (!m_LastActionsReceived.ContainsKey(info.episodeId)) + { + m_LastActionsReceived[info.episodeId] = ActionBuffers.Empty; + } + if (info.done) + { + // If the agent is done, we remove the key from the last action dictionary since no action + // should be taken. + m_LastActionsReceived.Remove(info.episodeId); + } + } + + public void DecideBatch() + { + var currentBatchSize = m_Infos.Count; + if (currentBatchSize == 0) + { + return; + } + if (!m_ObservationsInitialized) + { + // Just grab the first agent in the collection (any will suffice, really). + // We check for an empty Collection above, so this will always return successfully. + var firstInfo = m_Infos[0]; + m_TensorGenerator.InitializeObservations(firstInfo.sensors); + m_ObservationsInitialized = true; + } + + Profiler.BeginSample("ModelRunner.DecideAction"); + Profiler.BeginSample(m_ModelName); + + Profiler.BeginSample($"GenerateTensors"); + // Prepare the input tensors to be feed into the engine + m_TensorGenerator.GenerateTensors(m_InferenceInputs, currentBatchSize, m_Infos); + Profiler.EndSample(); + + Profiler.BeginSample($"PrepareSentisInputs"); + PrepareSentisInputs(m_InferenceInputs); + Profiler.EndSample(); + + // Execute the Model + Profiler.BeginSample($"ExecuteGraph"); + foreach (var kv in m_InputsByName) + { + m_Engine.SetInput(kv.Key, kv.Value); + } + m_Engine.Schedule(); + Profiler.EndSample(); + + Profiler.BeginSample($"FetchSentisOutputs"); + FetchSentisOutputs(m_OutputNames); + Profiler.EndSample(); + + Profiler.BeginSample($"ApplyTensors"); + // Update the outputs + m_TensorApplier.ApplyTensors(m_InferenceOutputs, m_OrderedAgentsRequestingDecisions, m_LastActionsReceived); + Profiler.EndSample(); + + Profiler.EndSample(); // end name + Profiler.EndSample(); // end ModelRunner.DecideAction + + m_Infos.Clear(); + + m_OrderedAgentsRequestingDecisions.Clear(); + } + + public bool HasModel(ModelAsset other, InferenceDevice otherInferenceDevice) + { + return m_Model == other && m_InferenceDevice == otherInferenceDevice; + } + + public ActionBuffers GetAction(int agentId) + { + if (m_LastActionsReceived.ContainsKey(agentId)) + { + return m_LastActionsReceived[agentId]; + } + return ActionBuffers.Empty; + } + } +} diff --git a/com.unity.ml-agents/Runtime/Inference/ModelRunner.cs.meta b/com.unity.ml-agents/Runtime/Inference/ModelRunner.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..e4e8e67539617360ecc0cb9d2a72d3a29f59991d Binary files /dev/null and b/com.unity.ml-agents/Runtime/Inference/ModelRunner.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Inference/SentisModelInfo.cs b/com.unity.ml-agents/Runtime/Inference/SentisModelInfo.cs new file mode 100644 index 0000000000000000000000000000000000000000..9eeb763fc8130f8a916284357b6e92a643310d02 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Inference/SentisModelInfo.cs @@ -0,0 +1,481 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Unity.InferenceEngine; +using FailedCheck = Unity.MLAgents.Inference.SentisModelParamLoader.FailedCheck; + +namespace Unity.MLAgents.Inference +{ + /// + /// Sentis Model utility methods. + /// + internal class SentisModelInfo : IDisposable + { + public string[] InputNames; + public string[] OutputNames; + public int Version; + public int NumVisualInputs; + public int MemorySize; + public bool HasContinuousOutputs; + public bool HasDiscreteOutputs; + public string ContinuousOutputName; + public string DiscreteOutputName; + public bool SupportsContinuousAndDiscrete; + public int ContinuousOutputSize; + public int DiscreteOutputSize; + Worker m_Worker; + Model m_Model; + bool m_DeterministicInference; + Dictionary m_ModelInputTensors; + Dictionary m_ModelOutputTensors; + + /// + /// Initializes a Sentis Model Info Object. This can be used to get information about the Sentis Model. + /// + /// The Sentis Model + /// Whether to use deterministic inference. + public SentisModelInfo(Model model, bool deterministicInference = false) + { + m_ModelOutputTensors = new Dictionary(); + m_Model = model; + m_DeterministicInference = deterministicInference; + m_Worker = new Worker(m_Model, DeviceType.CPU); + var inputTensors = GetInputTensors(); + m_ModelInputTensors = PrepareInputs(inputTensors); + foreach (var kv in m_ModelInputTensors) + { + m_Worker.SetInput(kv.Key, kv.Value); + } + m_Worker.Schedule(); + CacheModelInfo(); + } + + static Dictionary PrepareInputs(IReadOnlyList infInputs) + { + Dictionary inputs = new Dictionary(); + inputs.Clear(); + for (var i = 0; i < infInputs.Count; i++) + { + var inp = infInputs[i]; + var newTensorShape = new TensorShape(inp.shape.Select(i => (int)i).ToArray()); + inp.data = TensorUtils.CreateEmptyTensor(newTensorShape, inp.DType); + TensorUtils.FillTensorBatch(inp, 0, 0f); + inputs[inp.name] = inp.data; + } + + return inputs; + } + + + /// + /// Generates the Tensor inputs that are expected to be present in the Model. + /// + /// TensorProxy IEnumerable with the expected Tensor inputs. + public IReadOnlyList GetInputTensors() + { + var tensors = new List(); + + if (m_Model == null) + return tensors; + + foreach (var input in m_Model.inputs) + { + tensors.Add(new TensorProxy + { + name = input.name, + valueType = TensorProxy.TensorType.FloatingPoint, + data = null, + shape = input.shape.ToArray() + }); + } + + tensors.Sort((el1, el2) => string.Compare(el1.name, el2.name, StringComparison.InvariantCulture)); + + return tensors; + } + + /// + /// Gets the Discrete Action Output Shape as a Tensor. + /// + /// `Tensor` representation of the discret Action Ouptut Shape. + public Tensor GetDiscreteActionOutputShape() + { + return (Tensor)GetTensorByName(TensorNames.DiscreteActionOutputShape); + } + + void CacheModelInfo() + { + CacheOutputTensors(); + InputNames = GetInputNames(); + Version = GetVersion(); + NumVisualInputs = GetNumVisualInputs(); + OutputNames = GetOutputNames(); + MemorySize = GetMemorySize(); + HasContinuousOutputs = CheckContinuousOutputs(); + HasDiscreteOutputs = CheckDiscreteOutputs(); + ContinuousOutputName = GetContinuousOutputName(); + DiscreteOutputName = GetDiscreteOutputName(); + SupportsContinuousAndDiscrete = CheckSupportsContinuousAndDiscrete(); + ContinuousOutputSize = CheckContinuousOutputSize(); + DiscreteOutputSize = CheckDiscreteOutputSize(); + } + + void CacheOutputTensors() + { + foreach (var output in m_Model.outputs) + { + var outputName = output.name; + Tensor outputTensor = null; + m_Worker.CopyOutput(outputName, ref outputTensor); + outputTensor.CompleteAllPendingOperations(); + m_ModelOutputTensors.Add(outputName, outputTensor); + } + } + + Tensor GetTensorByName(string name) + { + try + { + return m_ModelOutputTensors[name]; + } + catch (KeyNotFoundException) + { + return null; + } + + } + + string[] GetInputNames() + { + var names = new List(); + + if (m_Model == null) + return names.ToArray(); + + foreach (var input in m_Model.inputs) + { + names.Add(input.name); + } + + names.Sort(StringComparer.InvariantCulture); + + return names.ToArray(); + } + + int GetVersion() + { + var version = GetTensorByNameAsInt(TensorNames.VersionNumber); + return version > 0 ? version : -1; + } + + int GetMemorySize() + { + return GetTensorByNameAsInt(TensorNames.MemorySize); + } + + int GetTensorByNameAsInt(string name) + { + var tensor = GetTensorByName(name); + var tensorAsInt = 0; + if (tensor != null) + tensorAsInt = (int)((Tensor)tensor)[0]; + return tensorAsInt; + } + + int GetNumVisualInputs() + { + var count = 0; + if (m_Model == null) + return count; + + foreach (var input in m_Model.inputs) + { + if (input.name.StartsWith(TensorNames.VisualObservationPlaceholderPrefix)) + { + count++; + } + } + + return count; + } + + string[] GetOutputNames() + { + var names = new List(); + + if (m_Model == null) + { + return names.ToArray(); + } + + if (CheckContinuousOutputs()) + { + names.Add(GetContinuousOutputName()); + } + if (CheckDiscreteOutputs()) + { + names.Add(GetDiscreteOutputName()); + } + + var modelVersion = GetVersion(); + + var memory = GetMemorySize(); + + if (memory > 0) + { + names.Add(TensorNames.RecurrentOutput); + } + + names.Sort(StringComparer.InvariantCulture); + + return names.ToArray(); + } + + bool CheckContinuousOutputs() + { + if (m_Model == null) + return false; + if (!CheckSupportsContinuousAndDiscrete()) + { + return ((Tensor)GetTensorByName(TensorNames.IsContinuousControlDeprecated))[0] > 0; + } + bool hasStochasticOutput = !m_DeterministicInference && + OutputsContainName(m_Model.outputs, TensorNames.ContinuousActionOutput); + bool hasDeterministicOutput = m_DeterministicInference && + OutputsContainName(m_Model.outputs, TensorNames.DeterministicContinuousActionOutput); + + return (hasStochasticOutput || hasDeterministicOutput) && + GetTensorByNameAsInt(TensorNames.ContinuousActionOutputShape) > 0; + } + + static bool OutputsContainName(List outputs, string name) + { + foreach (var output in outputs) + { + if (output.name.Contains(name)) + { + return true; + } + } + + return false; + } + + int CheckContinuousOutputSize() + { + if (m_Model == null) + return 0; + if (!CheckSupportsContinuousAndDiscrete()) + { + return ((Tensor)GetTensorByName(TensorNames.IsContinuousControlDeprecated))[0] > 0 ? ((Tensor)GetTensorByName(TensorNames.ActionOutputShapeDeprecated))[0] : 0; + } + else + { + var continuousOutputShape = GetTensorByName(TensorNames.ContinuousActionOutputShape); + return continuousOutputShape == null ? 0 : (int)((Tensor)continuousOutputShape)[0]; + } + } + + string GetContinuousOutputName() + { + if (m_Model == null) + return null; + if (!CheckSupportsContinuousAndDiscrete()) + { + return TensorNames.ActionOutputDeprecated; + } + return m_DeterministicInference ? TensorNames.DeterministicContinuousActionOutput : TensorNames.ContinuousActionOutput; + } + + bool CheckDiscreteOutputs() + { + if (m_Model == null) + return false; + if (!CheckSupportsContinuousAndDiscrete()) + { + return ((Tensor)GetTensorByName(TensorNames.IsContinuousControlDeprecated))[0] == 0; + } + else + { + bool hasStochasticOutput = !m_DeterministicInference && + OutputsContainName(m_Model.outputs, TensorNames.DiscreteActionOutput); + bool hasDeterministicOutput = m_DeterministicInference && + OutputsContainName(m_Model.outputs, TensorNames.DeterministicDiscreteActionOutput); + return (hasStochasticOutput || hasDeterministicOutput) && + CheckDiscreteOutputSize() > 0; + } + } + + int CheckDiscreteOutputSize() + { + if (m_Model == null) + return 0; + if (!CheckSupportsContinuousAndDiscrete()) + { + return ((Tensor)GetTensorByName(TensorNames.IsContinuousControlDeprecated))[0] > 0 ? 0 : ((Tensor)GetTensorByName(TensorNames.ActionOutputShapeDeprecated))[0]; + } + var discreteOutputShape = GetTensorByName(TensorNames.DiscreteActionOutputShape); + if (discreteOutputShape == null) + { + return 0; + } + int result = 0; + for (int i = 0; i < discreteOutputShape.Length(); i++) + { + result += (int)((Tensor)discreteOutputShape)[i]; + } + return result; + } + + string GetDiscreteOutputName() + { + if (m_Model == null) + return null; + if (!CheckSupportsContinuousAndDiscrete()) + { + return TensorNames.ActionOutputDeprecated; + } + else + { + return m_DeterministicInference ? TensorNames.DeterministicDiscreteActionOutput : TensorNames.DiscreteActionOutput; + } + } + + bool CheckSupportsContinuousAndDiscrete() + { + return m_Model == null || + OutputsContainName(m_Model.outputs, TensorNames.ContinuousActionOutput) || + OutputsContainName(m_Model.outputs, TensorNames.DiscreteActionOutput); + } + + + /// + /// Check if the model contains all the expected input/output tensors. + /// + /// Output list of failure messages + /// True if the model contains all the expected tensors. + /// TODO: add checks for deterministic actions + /// TODO: add checks for deterministic actions + public bool CheckExpectedTensors(List failedModelChecks) + { + // Check the presence of model version + var modelApiVersionTensor = GetTensorByName(TensorNames.VersionNumber); + if (modelApiVersionTensor == null) + { + failedModelChecks.Add( + FailedCheck.Warning($"Required constant \"{TensorNames.VersionNumber}\" was not found in the model file.") + ); + return false; + } + + // Check the presence of memory size + var memorySizeTensor = GetTensorByName(TensorNames.MemorySize); + if (memorySizeTensor == null) + { + failedModelChecks.Add( + FailedCheck.Warning($"Required constant \"{TensorNames.MemorySize}\" was not found in the model file.") + ); + return false; + } + + // Check the presence of action output tensor + if (!OutputsContainName(m_Model.outputs, TensorNames.ActionOutputDeprecated) && + !OutputsContainName(m_Model.outputs, TensorNames.ContinuousActionOutput) && + !OutputsContainName(m_Model.outputs, TensorNames.DiscreteActionOutput) && + !OutputsContainName(m_Model.outputs, TensorNames.DeterministicContinuousActionOutput) && + !OutputsContainName(m_Model.outputs, TensorNames.DeterministicDiscreteActionOutput)) + { + failedModelChecks.Add( + FailedCheck.Warning("The model does not contain any Action Output Node.") + ); + return false; + } + + // Check the presence of action output shape tensor + if (!CheckSupportsContinuousAndDiscrete()) + { + if (GetTensorByName(TensorNames.ActionOutputShapeDeprecated) == null) + { + failedModelChecks.Add( + FailedCheck.Warning("The model does not contain any Action Output Shape Node.") + ); + return false; + } + if (GetTensorByName(TensorNames.IsContinuousControlDeprecated) == null) + { + failedModelChecks.Add( + FailedCheck.Warning($"Required constant \"{TensorNames.IsContinuousControlDeprecated}\" was " + + "not found in the model file. " + + "This is only required for model that uses a deprecated model format.") + ); + return false; + } + } + else + { + if (OutputsContainName(m_Model.outputs, TensorNames.ContinuousActionOutput)) + { + if (GetTensorByName(TensorNames.ContinuousActionOutputShape) == null) + { + failedModelChecks.Add( + FailedCheck.Warning("The model uses continuous action but does not contain Continuous Action Output Shape Node.") + ); + return false; + } + else if (!CheckContinuousOutputs()) + { + var actionType = m_DeterministicInference ? "deterministic" : "stochastic"; + var actionName = m_DeterministicInference ? "Deterministic" : ""; + failedModelChecks.Add( + FailedCheck.Warning($"The model uses {actionType} inference but does not contain {actionName} Continuous Action Output Tensor. Uncheck `Deterministic inference` flag..") + ); + return false; + } + } + + if (OutputsContainName(m_Model.outputs, TensorNames.DiscreteActionOutput)) + { + if (GetTensorByName(TensorNames.DiscreteActionOutputShape) == null) + { + failedModelChecks.Add( + FailedCheck.Warning("The model uses discrete action but does not contain Discrete Action Output Shape Node.") + ); + return false; + } + else if (!CheckDiscreteOutputs()) + { + var actionType = m_DeterministicInference ? "deterministic" : "stochastic"; + var actionName = m_DeterministicInference ? "Deterministic" : ""; + failedModelChecks.Add( + FailedCheck.Warning($"The model uses {actionType} inference but does not contain {actionName} Discrete Action Output Tensor. Uncheck `Deterministic inference` flag.") + ); + return false; + } + } + } + return true; + } + + /// + /// Disposes of the Sentis Model Info owned Tensors. + /// + public void Dispose() + { + m_Worker?.Dispose(); + + foreach (var key in m_ModelInputTensors.Keys) + { + m_ModelInputTensors[key].Dispose(); + } + + m_ModelInputTensors.Clear(); + + foreach (var key in m_ModelOutputTensors.Keys) + { + m_ModelOutputTensors[key].Dispose(); + } + + m_ModelOutputTensors.Clear(); + } + } +} diff --git a/com.unity.ml-agents/Runtime/Inference/SentisModelInfo.cs.meta b/com.unity.ml-agents/Runtime/Inference/SentisModelInfo.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..d43474fb469e071d23fdbf940fbac3023fe9b740 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Inference/SentisModelInfo.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Inference/SentisModelParamLoader.cs b/com.unity.ml-agents/Runtime/Inference/SentisModelParamLoader.cs new file mode 100644 index 0000000000000000000000000000000000000000..b33c6f66fe04caeb3faf14b9553d8a0956a793aa --- /dev/null +++ b/com.unity.ml-agents/Runtime/Inference/SentisModelParamLoader.cs @@ -0,0 +1,911 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Unity.InferenceEngine; +using Unity.MLAgents.Actuators; +using Unity.MLAgents.Sensors; +using Unity.MLAgents.Policies; + +namespace Unity.MLAgents.Inference +{ + /// + /// Prepares the Tensors for the Learning Brain and exposes a list of failed checks if Model + /// and BrainParameters are incompatible. + /// + internal class SentisModelParamLoader + { + internal enum ModelApiVersion + { + /// + /// ML-Agents model version for versions 1.x.y + /// The observations are split between vector and visual observations + /// There are legacy action outputs for discrete and continuous actions + /// LSTM inputs and outputs are handled by Sentis + /// + MLAgents1_0 = 2, + + /// + /// All observations are treated the same and named obs_{i} with i being + /// the sensor index + /// Legacy "action" output is no longer present + /// LSTM inputs and outputs are treated like regular inputs and outputs + /// and no longer managed by Sentis + /// + MLAgents2_0 = 3, + MinSupportedVersion = MLAgents1_0, + MaxSupportedVersion = MLAgents2_0 + } + + internal class FailedCheck + { + public enum CheckTypeEnum + { + Info = 0, + Warning = 1, + Error = 2 + } + public CheckTypeEnum CheckType; + public string Message; + public static FailedCheck Info(string message) + { + return new FailedCheck { CheckType = CheckTypeEnum.Info, Message = message }; + } + + public static FailedCheck Warning(string message) + { + return new FailedCheck { CheckType = CheckTypeEnum.Warning, Message = message }; + } + + public static FailedCheck Error(string message) + { + return new FailedCheck { CheckType = CheckTypeEnum.Error, Message = message }; + } + } + + /// + /// Checks that a model has the appropriate version. + /// + /// + /// The Sentis engine model for loading static parameters + /// + /// A FailedCheck containing the error message if the version of the model does not mach, else null + public static FailedCheck CheckModelVersion(SentisModelInfo modelInfo) + { + var modelApiVersion = modelInfo.Version; + if (modelApiVersion < (int)ModelApiVersion.MinSupportedVersion) + { + return FailedCheck.Error( + "Model was trained with a older version of the trainer than is supported. " + + "Either retrain with an newer trainer, or use an older version of com.unity.ml-agents.\n" + + $"Model version: {modelApiVersion} Minimum supported version: {(int)ModelApiVersion.MinSupportedVersion}" + ); + } + + if (modelApiVersion > (int)ModelApiVersion.MaxSupportedVersion) + { + return FailedCheck.Error( + "Model was trained with a newer version of the trainer than is supported. " + + "Either retrain with an older trainer, or update to a newer version of com.unity.ml-agents.\n" + + $"Model version: {modelApiVersion} Maximum supported version: {(int)ModelApiVersion.MaxSupportedVersion}" + ); + } + + var memorySize = modelInfo.MemorySize; + + if (modelApiVersion == (int)ModelApiVersion.MLAgents1_0 && memorySize > 0) + { + // This block is to make sure that models that are trained with MLAgents version 1.x and have + // an LSTM (i.e. use the Sentis _c and _h inputs and outputs) will not work with MLAgents version + // 2.x. This is because Sentis version 2.x will eventually drop support for the _c and _h inputs + // and only ML-Agents 2.x models will be compatible. + return FailedCheck.Error( + "Models from com.unity.ml-agents 1.x that use recurrent neural networks are not supported in newer versions. " + + "Either retrain with an newer trainer, or use an older version of com.unity.ml-agents.\n" + ); + } + return null; + } + + /// + /// Factory for the ModelParamLoader : Creates a ModelParamLoader and runs the checks + /// on it. + /// + /// + /// The Sentis engine model for loading static parameters + /// + /// + /// The BrainParameters that are used verify the compatibility with the InferenceEngine + /// + /// Attached sensor components + /// Attached actuator components + /// Sum of the sizes of all ObservableAttributes. + /// BehaviorType or the Agent to check. + /// Inference only: set to true if the action selection from model should be + /// deterministic. + /// A IEnumerable of the checks that failed + public static IEnumerable CheckModel( + Model model, + BrainParameters brainParameters, + ISensor[] sensors, + ActuatorComponent[] actuatorComponents, + int observableAttributeTotalSize = 0, + BehaviorType behaviorType = BehaviorType.Default, + bool deterministicInference = false + ) + { + List failedModelChecks = new List(); + if (model == null) + { + var errorMsg = "There is no model for this Brain; cannot run inference. "; + if (behaviorType == BehaviorType.InferenceOnly) + { + errorMsg += "Either assign a model, or change to a different Behavior Type."; + } + else + { + errorMsg += "(But can still train)"; + } + failedModelChecks.Add(FailedCheck.Info(errorMsg)); + return failedModelChecks; + } + using var modelInfo = new SentisModelInfo(model, deterministicInference); + var hasExpectedTensors = modelInfo.CheckExpectedTensors(failedModelChecks); + if (!hasExpectedTensors) + { + return failedModelChecks; + } + + var modelApiVersion = modelInfo.Version; + var versionCheck = CheckModelVersion(modelInfo); + if (versionCheck != null) + { + failedModelChecks.Add(versionCheck); + } + + var memorySize = modelInfo.MemorySize; + if (memorySize == -1) + { + failedModelChecks.Add(FailedCheck.Warning($"Missing node in the model provided : {TensorNames.MemorySize}" + )); + return failedModelChecks; + } + + if (modelApiVersion == (int)ModelApiVersion.MLAgents1_0) + { + failedModelChecks.AddRange( + CheckInputTensorPresenceLegacy(model, brainParameters, memorySize, sensors) + ); + failedModelChecks.AddRange( + CheckInputTensorShapeLegacy(model, brainParameters, sensors, observableAttributeTotalSize) + ); + } + else if (modelApiVersion == (int)ModelApiVersion.MLAgents2_0) + { + failedModelChecks.AddRange( + CheckInputTensorPresence(model, brainParameters, memorySize, sensors, deterministicInference) + ); + failedModelChecks.AddRange( + CheckInputTensorShape(model, brainParameters, sensors, observableAttributeTotalSize) + ); + } + + + failedModelChecks.AddRange( + CheckOutputTensorShape(model, brainParameters, actuatorComponents) + ); + + failedModelChecks.AddRange( + CheckOutputTensorPresence(model, memorySize, deterministicInference) + ); + return failedModelChecks; + } + + /// + /// Generates failed checks that correspond to inputs expected by the model that are not + /// present in the BrainParameters. Tests the models created with the API of version 1.X + /// + /// + /// The Sentis engine model for loading static parameters + /// + /// + /// The BrainParameters that are used verify the compatibility with the InferenceEngine + /// + /// + /// The memory size that the model is expecting. + /// + /// Array of attached sensor components + /// + /// A IEnumerable of the checks that failed + /// + static IEnumerable CheckInputTensorPresenceLegacy( + Model model, + BrainParameters brainParameters, + int memory, + ISensor[] sensors + ) + { + using var modelInfo = new SentisModelInfo(model); + var failedModelChecks = new List(); + var tensorsNames = modelInfo.InputNames; + + // If there is no Vector Observation Input but the Brain Parameters expect one. + if ((brainParameters.VectorObservationSize != 0) && + (!tensorsNames.Contains(TensorNames.VectorObservationPlaceholder))) + { + failedModelChecks.Add( + FailedCheck.Warning("The model does not contain a Vector Observation Placeholder Input. " + + "You must set the Vector Observation Space Size to 0.") + ); + } + + // If there are not enough Visual Observation Input compared to what the + // sensors expect. + var visObsIndex = 0; + for (var sensorIndex = 0; sensorIndex < sensors.Length; sensorIndex++) + { + var sensor = sensors[sensorIndex]; + if (sensor.GetObservationSpec().Shape.Length == 3) + { + if (!tensorsNames.Contains( + TensorNames.GetVisualObservationName(visObsIndex))) + { + failedModelChecks.Add( + FailedCheck.Warning("The model does not contain a Visual Observation Placeholder Input " + + $"for sensor component {visObsIndex} ({sensor.GetType().Name}).") + ); + } + visObsIndex++; + } + if (sensor.GetObservationSpec().Shape.Length == 2) + { + if (!tensorsNames.Contains( + TensorNames.GetObservationName(sensorIndex))) + { + failedModelChecks.Add( + FailedCheck.Warning("The model does not contain an Observation Placeholder Input " + + $"for sensor component {sensorIndex} ({sensor.GetType().Name}).") + ); + } + } + } + + var expectedVisualObs = modelInfo.NumVisualInputs; + // Check if there's not enough visual sensors (too many would be handled above) + if (expectedVisualObs > visObsIndex) + { + failedModelChecks.Add( + FailedCheck.Warning($"The model expects {expectedVisualObs} visual inputs," + + $" but only found {visObsIndex} visual sensors.") + ); + } + + // If the model has a non-negative memory size but requires a recurrent input + if (memory > 0) + { + if (!tensorsNames.Any(x => x.EndsWith("_h")) || + !tensorsNames.Any(x => x.EndsWith("_c"))) + { + failedModelChecks.Add( + FailedCheck.Warning("The model does not contain a Recurrent Input Node but has memory_size.") + ); + } + } + + // If the model uses discrete control but does not have an input for action masks + if (modelInfo.HasDiscreteOutputs) + { + if (!tensorsNames.Contains(TensorNames.ActionMaskPlaceholder)) + { + failedModelChecks.Add( + FailedCheck.Warning("The model does not contain an Action Mask but is using Discrete Control.") + ); + } + } + return failedModelChecks; + } + + /// + /// Generates failed checks that correspond to inputs expected by the model that are not + /// present in the BrainParameters. + /// + /// + /// The Sentis engine model for loading static parameters + /// + /// + /// The BrainParameters that are used verify the compatibility with the InferenceEngine + /// + /// + /// The memory size that the model is expecting. + /// + /// Array of attached sensor components + /// Inference only: set to true if the action selection from model should be + /// Deterministic. + /// + /// A IEnumerable of the checks that failed + /// + static IEnumerable CheckInputTensorPresence( + Model model, + BrainParameters brainParameters, + int memory, + ISensor[] sensors, + bool deterministicInference = false + ) + { + using var modelInfo = new SentisModelInfo(model); + var failedModelChecks = new List(); + var tensorsNames = modelInfo.InputNames; + for (var sensorIndex = 0; sensorIndex < sensors.Length; sensorIndex++) + { + if (!tensorsNames.Contains( + TensorNames.GetObservationName(sensorIndex))) + { + var sensor = sensors[sensorIndex]; + failedModelChecks.Add( + FailedCheck.Warning("The model does not contain an Observation Placeholder Input " + + $"for sensor component {sensorIndex} ({sensor.GetType().Name}).") + ); + } + } + + // If the model has a non-negative memory size but requires a recurrent input + if (memory > 0) + { + var modelVersion = modelInfo.Version; + if (!tensorsNames.Any(x => x == TensorNames.RecurrentInPlaceholder)) + { + failedModelChecks.Add( + FailedCheck.Warning("The model does not contain a Recurrent Input Node but has memory_size.") + ); + } + } + + // If the model uses discrete control but does not have an input for action masks + if (modelInfo.HasDiscreteOutputs) + { + if (!tensorsNames.Contains(TensorNames.ActionMaskPlaceholder)) + { + failedModelChecks.Add( + FailedCheck.Warning("The model does not contain an Action Mask but is using Discrete Control.") + ); + } + } + return failedModelChecks; + } + + /// + /// Generates failed checks that correspond to outputs expected by the model that are not + /// present in the BrainParameters. + /// + /// + /// The Sentis engine model for loading static parameters + /// + /// The memory size that the model is expecting/ + /// Inference only: set to true if the action selection from model should be + /// deterministic. + /// + /// A IEnumerable of the checks that failed + /// + static IEnumerable CheckOutputTensorPresence(Model model, int memory, bool deterministicInference = false) + { + using var modelInfo = new SentisModelInfo(model, deterministicInference); + var failedModelChecks = new List(); + + // If there is no Recurrent Output but the model is Recurrent. + if (memory > 0) + { + var allOutputs = modelInfo.OutputNames.ToList(); + if (!allOutputs.Any(x => x == TensorNames.RecurrentOutput)) + { + failedModelChecks.Add( + FailedCheck.Warning("The model does not contain a Recurrent Output Node but has memory_size.") + ); + } + } + return failedModelChecks; + } + + /// + /// Checks that the shape of the visual observation input placeholder is the same as the corresponding sensor. + /// + /// The tensor that is expected by the model + /// The sensor that produces the visual observation. + /// + /// If the Check failed, returns a string containing information about why the + /// check failed. If the check passed, returns null. + /// + static FailedCheck CheckVisualObsShape( + TensorProxy tensorProxy, ISensor sensor) + { + var shape = sensor.GetObservationSpec().Shape; + var heightBp = shape[1]; + var widthBp = shape[2]; + var pixelBp = shape[0]; + var heightT = tensorProxy.Height; + var widthT = tensorProxy.Width; + var pixelT = tensorProxy.Channels; + if ((widthBp != widthT) || (heightBp != heightT) || (pixelBp != pixelT)) + { + return FailedCheck.Warning($"The visual Observation of the model does not match. " + + $"Received TensorProxy of shape [?x{widthBp}x{heightBp}x{pixelBp}] but " + + $"was expecting [?x{widthT}x{heightT}x{pixelT}] for the {sensor.GetName()} Sensor." + ); + } + return null; + } + + /// + /// Checks that the shape of the rank 2 observation input placeholder is the same as the corresponding sensor. + /// + /// The tensor that is expected by the model + /// The sensor that produces the visual observation. + /// + /// If the Check failed, returns a string containing information about why the + /// check failed. If the check passed, returns null. + /// + static FailedCheck CheckRankTwoObsShape( + TensorProxy tensorProxy, ISensor sensor) + { + var shape = sensor.GetObservationSpec().Shape; + var dim1Bp = shape[0]; + var dim2Bp = shape[1]; + var dim1T = tensorProxy.Channels; + var dim2T = tensorProxy.Width; + var dim3T = tensorProxy.Height; + if ((dim1Bp != dim1T) || (dim2Bp != dim2T)) + { + var proxyDimStr = $"[?x{dim1T}x{dim2T}]"; + if (dim3T > 1) + { + proxyDimStr = $"[?x{dim3T}x{dim2T}x{dim1T}]"; + } + return FailedCheck.Warning($"An Observation of the model does not match. " + + $"Received TensorProxy of shape [?x{dim1Bp}x{dim2Bp}] but " + + $"was expecting {proxyDimStr} for the {sensor.GetName()} Sensor." + ); + } + return null; + } + + /// + /// Checks that the shape of the rank 2 observation input placeholder is the same as the corresponding sensor. + /// + /// The tensor that is expected by the model + /// The sensor that produces the visual observation. + /// + /// If the Check failed, returns a string containing information about why the + /// check failed. If the check passed, returns null. + /// + static FailedCheck CheckRankOneObsShape( + TensorProxy tensorProxy, ISensor sensor) + { + var shape = sensor.GetObservationSpec().Shape; + var dim1Bp = shape[0]; + var dim1T = tensorProxy.Channels; + var dim2T = tensorProxy.Width; + var dim3T = tensorProxy.Height; + if ((dim1Bp != dim1T)) + { + var proxyDimStr = $"[?x{dim1T}]"; + if (dim2T > 1) + { + proxyDimStr = $"[?x{dim1T}x{dim2T}]"; + } + if (dim3T > 1) + { + proxyDimStr = $"[?x{dim3T}x{dim2T}x{dim1T}]"; + } + return FailedCheck.Warning($"An Observation of the model does not match. " + + $"Received TensorProxy of shape [?x{dim1Bp}] but " + + $"was expecting {proxyDimStr} for the {sensor.GetName()} Sensor." + ); + } + return null; + } + + /// + /// Generates failed checks that correspond to inputs shapes incompatibilities between + /// the model and the BrainParameters. Tests the models created with the API of version 1.X + /// + /// + /// The Sentis engine model for loading static parameters + /// + /// + /// The BrainParameters that are used verify the compatibility with the InferenceEngine + /// + /// Attached sensors + /// Sum of the sizes of all ObservableAttributes. + /// A IEnumerable of the checks that failed + static IEnumerable CheckInputTensorShapeLegacy( + Model model, BrainParameters brainParameters, ISensor[] sensors, + int observableAttributeTotalSize) + { + using var modelInfo = new SentisModelInfo(model); + var failedModelChecks = new List(); + var tensorTester = + new Dictionary>() + { + {TensorNames.VectorObservationPlaceholder, CheckVectorObsShapeLegacy}, + {TensorNames.PreviousActionPlaceholder, CheckPreviousActionShape}, + {TensorNames.RandomNormalEpsilonPlaceholder, ((bp, tensor, scs, i) => null)}, + {TensorNames.ActionMaskPlaceholder, ((bp, tensor, scs, i) => null)}, + {TensorNames.SequenceLengthPlaceholder, ((bp, tensor, scs, i) => null)}, + {TensorNames.RecurrentInPlaceholder, ((bp, tensor, scs, i) => null)}, + }; + + // foreach (var mem in model.memories) + // { + // tensorTester[mem.input] = ((bp, tensor, scs, i) => null); + // } + + var visObsIndex = 0; + for (var sensorIndex = 0; sensorIndex < sensors.Length; sensorIndex++) + { + var sens = sensors[sensorIndex]; + if (sens.GetObservationSpec().Shape.Length == 3) + { + tensorTester[TensorNames.GetVisualObservationName(visObsIndex)] = + (bp, tensor, scs, i) => CheckVisualObsShape(tensor, sens); + visObsIndex++; + } + if (sens.GetObservationSpec().Shape.Length == 2) + { + tensorTester[TensorNames.GetObservationName(sensorIndex)] = + (bp, tensor, scs, i) => CheckRankTwoObsShape(tensor, sens); + } + } + + // If the model expects an input but it is not in this list + foreach (var tensor in modelInfo.GetInputTensors()) + { + if (!tensorTester.ContainsKey(tensor.name)) + { + if (!tensor.name.Contains("visual_observation")) + { + failedModelChecks.Add( + FailedCheck.Warning("Model contains an unexpected input named : " + tensor.name) + ); + } + } + else + { + var tester = tensorTester[tensor.name]; + var error = tester.Invoke(brainParameters, tensor, sensors, observableAttributeTotalSize); + if (error != null) + { + failedModelChecks.Add(error); + } + } + } + return failedModelChecks; + } + + /// + /// Checks that the shape of the Vector Observation input placeholder is the same in the + /// model and in the Brain Parameters. Tests the models created with the API of version 1.X + /// + /// + /// The BrainParameters that are used verify the compatibility with the InferenceEngine + /// + /// The tensor that is expected by the model + /// Array of attached sensor components + /// Sum of the sizes of all ObservableAttributes. + /// + /// If the Check failed, returns a string containing information about why the + /// check failed. If the check passed, returns null. + /// + static FailedCheck CheckVectorObsShapeLegacy( + BrainParameters brainParameters, TensorProxy tensorProxy, ISensor[] sensors, + int observableAttributeTotalSize) + { + var vecObsSizeBp = brainParameters.VectorObservationSize; + var numStackedVector = brainParameters.NumStackedVectorObservations; + var totalVecObsSizeT = tensorProxy.shape[tensorProxy.shape.Length - 1]; + + var totalVectorSensorSize = 0; + foreach (var sens in sensors) + { + if ((sens.GetObservationSpec().Shape.Length == 1)) + { + totalVectorSensorSize += sens.GetObservationSpec().Shape[0]; + } + } + + if (totalVectorSensorSize != totalVecObsSizeT) + { + var sensorSizes = ""; + foreach (var sensorComp in sensors) + { + if (sensorComp.GetObservationSpec().Shape.Length == 1) + { + var vecSize = sensorComp.GetObservationSpec().Shape[0]; + if (sensorSizes.Length == 0) + { + sensorSizes = $"[{vecSize}"; + } + else + { + sensorSizes += $", {vecSize}"; + } + } + } + + sensorSizes += "]"; + return FailedCheck.Warning( + $"Vector Observation Size of the model does not match. Was expecting {totalVecObsSizeT} " + + $"but received: \n" + + $"Vector observations: {vecObsSizeBp} x {numStackedVector}\n" + + $"Total [Observable] attributes: {observableAttributeTotalSize}\n" + + $"Sensor sizes: {sensorSizes}." + ); + } + return null; + } + + /// + /// Generates failed checks that correspond to inputs shapes incompatibilities between + /// the model and the BrainParameters. + /// + /// + /// The Sentis engine model for loading static parameters + /// + /// + /// The BrainParameters that are used verify the compatibility with the InferenceEngine + /// + /// Attached sensors + /// Sum of the sizes of all ObservableAttributes. + /// A IEnumerable of the checks that failed + static IEnumerable CheckInputTensorShape( + Model model, BrainParameters brainParameters, ISensor[] sensors, + int observableAttributeTotalSize) + { + using var modelInfo = new SentisModelInfo(model); + var failedModelChecks = new List(); + var tensorTester = + new Dictionary>() + { + {TensorNames.PreviousActionPlaceholder, CheckPreviousActionShape}, + {TensorNames.RandomNormalEpsilonPlaceholder, ((bp, tensor, scs, i) => null)}, + {TensorNames.ActionMaskPlaceholder, ((bp, tensor, scs, i) => null)}, + {TensorNames.SequenceLengthPlaceholder, ((bp, tensor, scs, i) => null)}, + {TensorNames.RecurrentInPlaceholder, ((bp, tensor, scs, i) => null)}, + }; + + // foreach (var mem in model.memories) + // { + // tensorTester[mem.input] = ((bp, tensor, scs, i) => null); + // } + + for (var sensorIndex = 0; sensorIndex < sensors.Length; sensorIndex++) + { + var sens = sensors[sensorIndex]; + if (sens.GetObservationSpec().Rank == 3) + { + tensorTester[TensorNames.GetObservationName(sensorIndex)] = + (bp, tensor, scs, i) => CheckVisualObsShape(tensor, sens); + } + if (sens.GetObservationSpec().Rank == 2) + { + tensorTester[TensorNames.GetObservationName(sensorIndex)] = + (bp, tensor, scs, i) => CheckRankTwoObsShape(tensor, sens); + } + if (sens.GetObservationSpec().Rank == 1) + { + tensorTester[TensorNames.GetObservationName(sensorIndex)] = + (bp, tensor, scs, i) => CheckRankOneObsShape(tensor, sens); + } + } + + // If the model expects an input but it is not in this list + foreach (var tensor in modelInfo.GetInputTensors()) + { + if (!tensorTester.ContainsKey(tensor.name)) + { + failedModelChecks.Add(FailedCheck.Warning("Model contains an unexpected input named : " + tensor.name + )); + } + else + { + var tester = tensorTester[tensor.name]; + var error = tester.Invoke(brainParameters, tensor, sensors, observableAttributeTotalSize); + if (error != null) + { + failedModelChecks.Add(error); + } + } + } + return failedModelChecks; + } + + /// + /// Checks that the shape of the Previous Vector Action input placeholder is the same in the + /// model and in the Brain Parameters. + /// + /// + /// The BrainParameters that are used verify the compatibility with the InferenceEngine + /// + /// The tensor that is expected by the model + /// Array of attached sensor components (unused). + /// Sum of the sizes of all ObservableAttributes (unused). + /// If the Check failed, returns a string containing information about why the + /// check failed. If the check passed, returns null. + static FailedCheck CheckPreviousActionShape( + BrainParameters brainParameters, TensorProxy tensorProxy, + ISensor[] sensors, int observableAttributeTotalSize) + { + var numberActionsBp = brainParameters.ActionSpec.NumDiscreteActions; + var numberActionsT = tensorProxy.shape[tensorProxy.shape.Length - 1]; + if (numberActionsBp != numberActionsT) + { + return FailedCheck.Warning("Previous Action Size of the model does not match. " + + $"Received {numberActionsBp} but was expecting {numberActionsT}." + ); + } + return null; + } + + /// + /// Generates failed checks that correspond to output shapes incompatibilities between + /// the model and the BrainParameters. + /// + /// + /// The Sentis engine model for loading static parameters + /// + /// + /// The BrainParameters that are used verify the compatibility with the InferenceEngine + /// + /// Array of attached actuator components. + /// + /// A IEnumerable of error messages corresponding to the incompatible shapes between model + /// and BrainParameters. + /// + static IEnumerable CheckOutputTensorShape( + Model model, + BrainParameters brainParameters, + ActuatorComponent[] actuatorComponents) + { + using var modelInfo = new SentisModelInfo(model); + var failedModelChecks = new List(); + + // If the model expects an output but it is not in this list + var modelContinuousActionSize = modelInfo.ContinuousOutputSize; + var continuousError = CheckContinuousActionOutputShape(brainParameters, actuatorComponents, modelContinuousActionSize); + if (continuousError != null) + { + failedModelChecks.Add(continuousError); + } + FailedCheck discreteError = null; + var modelApiVersion = modelInfo.Version; + if (modelApiVersion == (int)ModelApiVersion.MLAgents1_0) + { + var modelSumDiscreteBranchSizes = modelInfo.DiscreteOutputSize; + discreteError = CheckDiscreteActionOutputShapeLegacy(brainParameters, actuatorComponents, modelSumDiscreteBranchSizes); + } + if (modelApiVersion == (int)ModelApiVersion.MLAgents2_0) + { + var modelDiscreteBranches = modelInfo.GetDiscreteActionOutputShape(); + discreteError = CheckDiscreteActionOutputShape(brainParameters, actuatorComponents, modelDiscreteBranches); + } + + if (discreteError != null) + { + failedModelChecks.Add(discreteError); + } + + return failedModelChecks; + } + + /// + /// Checks that the shape of the discrete action output is the same in the + /// model and in the Brain Parameters. + /// + /// + /// The BrainParameters that are used verify the compatibility with the InferenceEngine + /// + /// Array of attached actuator components. + /// The Tensor of branch sizes. + /// + /// + /// If the Check failed, returns a string containing information about why the + /// check failed. If the check passed, returns null. + /// + static FailedCheck CheckDiscreteActionOutputShape( + BrainParameters brainParameters, ActuatorComponent[] actuatorComponents, Tensor modelDiscreteBranches) + { + var discreteActionBranches = brainParameters.ActionSpec.BranchSizes.ToList(); + foreach (var actuatorComponent in actuatorComponents) + { + var actionSpec = actuatorComponent.ActionSpec; + discreteActionBranches.AddRange(actionSpec.BranchSizes); + } + + int modelDiscreteBranchesLength = modelDiscreteBranches?.shape.length ?? 0; + if (modelDiscreteBranchesLength != discreteActionBranches.Count) + { + return FailedCheck.Warning("Discrete Action Size of the model does not match. The BrainParameters expect " + + $"{discreteActionBranches.Count} branches but the model contains {modelDiscreteBranchesLength}." + ); + } + + for (int i = 0; i < modelDiscreteBranchesLength; i++) + { + if (modelDiscreteBranches != null && modelDiscreteBranches[i] != discreteActionBranches[i]) + { + return FailedCheck.Warning($"The number of Discrete Actions of branch {i} does not match. " + + $"Was expecting {discreteActionBranches[i]} but the model contains {modelDiscreteBranches[i]} " + ); + } + } + return null; + } + + /// + /// Checks that the shape of the discrete action output is the same in the + /// model and in the Brain Parameters. Tests the models created with the API of version 1.X + /// + /// + /// The BrainParameters that are used verify the compatibility with the InferenceEngine + /// + /// Array of attached actuator components. + /// + /// The size of the discrete action output that is expected by the model. + /// + /// + /// If the Check failed, returns a string containing information about why the + /// check failed. If the check passed, returns null. + /// + static FailedCheck CheckDiscreteActionOutputShapeLegacy( + BrainParameters brainParameters, ActuatorComponent[] actuatorComponents, int modelSumDiscreteBranchSizes) + { + // TODO: check each branch size instead of sum of branch sizes + var sumOfDiscreteBranchSizes = brainParameters.ActionSpec.SumOfDiscreteBranchSizes; + + foreach (var actuatorComponent in actuatorComponents) + { + var actionSpec = actuatorComponent.ActionSpec; + sumOfDiscreteBranchSizes += actionSpec.SumOfDiscreteBranchSizes; + } + + if (modelSumDiscreteBranchSizes != sumOfDiscreteBranchSizes) + { + return FailedCheck.Warning("Discrete Action Size of the model does not match. The BrainParameters expect " + + $"{sumOfDiscreteBranchSizes} but the model contains {modelSumDiscreteBranchSizes}." + ); + } + return null; + } + + /// + /// Checks that the shape of the continuous action output is the same in the + /// model and in the Brain Parameters. + /// + /// + /// The BrainParameters that are used verify the compatibility with the InferenceEngine + /// + /// Array of attached actuator components. + /// + /// The size of the continuous action output that is expected by the model. + /// + /// If the Check failed, returns a string containing information about why the + /// check failed. If the check passed, returns null. + static FailedCheck CheckContinuousActionOutputShape( + BrainParameters brainParameters, ActuatorComponent[] actuatorComponents, int modelContinuousActionSize) + { + var numContinuousActions = brainParameters.ActionSpec.NumContinuousActions; + + foreach (var actuatorComponent in actuatorComponents) + { + var actionSpec = actuatorComponent.ActionSpec; + numContinuousActions += actionSpec.NumContinuousActions; + } + + if (modelContinuousActionSize != numContinuousActions) + { + return FailedCheck.Warning( + "Continuous Action Size of the model does not match. The BrainParameters and ActuatorComponents expect " + + $"{numContinuousActions} but the model contains {modelContinuousActionSize}." + ); + } + return null; + } + } +} diff --git a/com.unity.ml-agents/Runtime/Inference/SentisModelParamLoader.cs.meta b/com.unity.ml-agents/Runtime/Inference/SentisModelParamLoader.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..2029d8acbba6edecc7ab1197047aaab9eb48cd30 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Inference/SentisModelParamLoader.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Inference/TensorApplier.cs b/com.unity.ml-agents/Runtime/Inference/TensorApplier.cs new file mode 100644 index 0000000000000000000000000000000000000000..060bfc6d3a00d9cf5426bd059e8a640d24929fad --- /dev/null +++ b/com.unity.ml-agents/Runtime/Inference/TensorApplier.cs @@ -0,0 +1,112 @@ +using System.Collections.Generic; +using Unity.InferenceEngine; +using Unity.MLAgents.Actuators; + + +namespace Unity.MLAgents.Inference +{ + /// + /// Mapping between the output tensor names and the method that will use the + /// output tensors and the Agents present in the batch to update their action, memories and + /// value estimates. + /// A TensorApplier implements a Dictionary of strings (node names) to an Action. + /// This action takes as input the tensor and the Dictionary of Agent to AgentInfo for + /// the current batch. + /// + internal class TensorApplier + { + /// + /// A tensor Applier's Execute method takes a tensor and a Dictionary of Agent to AgentInfo. + /// Uses the data contained inside the tensor to modify the state of the Agent. The Tensors + /// are assumed to have the batch size on the first dimension and the agents to be ordered + /// the same way in the dictionary and in the tensor. + /// + public interface IApplier + { + /// + /// Applies the values in the Tensor to the Agents present in the agentInfos + /// + /// + /// The Tensor containing the data to be applied to the Agents + /// + /// List of Agents Ids that will be updated using the tensor's data + /// Dictionary of AgentId to Actions to be updated + void Apply(TensorProxy tensorProxy, IList actionIds, Dictionary lastActions); + } + + readonly Dictionary m_Dict = new Dictionary(); + + /// + /// Returns a new TensorAppliers object. + /// + /// Description of the actions for the Agent. + /// The seed the Appliers will be initialized with. + /// Tensor allocator + /// Dictionary of AgentInfo.id to memory used to pass to the inference model. + /// + /// Inference only: set to true if the action selection from model should be + /// deterministic. + public TensorApplier( + ActionSpec actionSpec, + int seed, + Dictionary> memories, + object sentisModel = null, + bool deterministicInference = false) + { + // If model is null, no inference to run and exception is thrown before reaching here. + if (sentisModel == null) + { + return; + } + + var model = (Model)sentisModel; + using var modelInfo = new SentisModelInfo(model, deterministicInference); + if (!modelInfo.SupportsContinuousAndDiscrete) + { + actionSpec.CheckAllContinuousOrDiscrete(); + } + if (actionSpec.NumContinuousActions > 0) + { + var tensorName = modelInfo.ContinuousOutputName; + m_Dict[tensorName] = new ContinuousActionOutputApplier(actionSpec); + } + var modelVersion = modelInfo.Version; + if (actionSpec.NumDiscreteActions > 0) + { + var tensorName = modelInfo.DiscreteOutputName; + if (modelVersion == (int)SentisModelParamLoader.ModelApiVersion.MLAgents1_0) + { + m_Dict[tensorName] = new LegacyDiscreteActionOutputApplier(actionSpec, seed); + } + if (modelVersion == (int)SentisModelParamLoader.ModelApiVersion.MLAgents2_0) + { + m_Dict[tensorName] = new DiscreteActionOutputApplier(actionSpec, seed); + } + } + m_Dict[TensorNames.RecurrentOutput] = new MemoryOutputApplier(memories); + } + + /// + /// Updates the state of the agents based on the data present in the tensor. + /// + /// Enumerable of tensors containing the data. + /// List of Agents Ids that will be updated using the tensor's data + /// Dictionary of AgentId to Actions to be updated + /// One of the tensor does not have an + /// associated applier. + public void ApplyTensors( + IReadOnlyList tensors, IList actionIds, Dictionary lastActions) + { + for (var tensorIndex = 0; tensorIndex < tensors.Count; tensorIndex++) + { + var tensor = tensors[tensorIndex]; + if (!m_Dict.ContainsKey(tensor.name)) + { + throw new UnityAgentsException( + $"Unknown tensorProxy expected as output : {tensor.name}"); + } + m_Dict[tensor.name].Apply(tensor, actionIds, lastActions); + } + } + } +} diff --git a/com.unity.ml-agents/Runtime/Inference/TensorApplier.cs.meta b/com.unity.ml-agents/Runtime/Inference/TensorApplier.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..d95eb26a15d74d5f307831e58af4183a51177c60 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Inference/TensorApplier.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Inference/TensorExtensions.cs b/com.unity.ml-agents/Runtime/Inference/TensorExtensions.cs new file mode 100644 index 0000000000000000000000000000000000000000..94a717eea540abd1ceb509f04f19cd6c939bd996 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Inference/TensorExtensions.cs @@ -0,0 +1,66 @@ +using Unity.InferenceEngine; + +namespace Unity.MLAgents.Inference +{ + internal static class TensorExtensions + { + // assumes NCHW (channel first) but might be NHWC + public static int Batch(this Tensor tensor) + { + return tensor.shape.Batch(); + } + + public static int Height(this Tensor tensor) + { + return tensor.shape.Height(); + } + + public static int Width(this Tensor tensor) + { + return tensor.shape.Width(); + } + + public static int Channels(this Tensor tensor) + { + return tensor.shape.Channels(); + } + + public static int Length(this Tensor tensor) + { + return tensor.shape.length; + } + } + + internal static class TensorShapeExtensions + { + public static int Batch(this TensorShape shape) + { + return shape.rank >= 1 ? shape[0] : 0; + } + + public static int Height(this TensorShape shape) + { + return shape.rank >= 4 ? shape[shape.rank - 2] : 0; + } + + public static int Width(this TensorShape shape) + { + return shape.rank >= 3 ? shape[shape.rank - 1] : 0; + } + + public static int Channels(this TensorShape shape) + { + return shape.rank is >= 2 and < 4 ? shape[1] : shape.rank >= 4 ? shape[shape.rank - 3] : 0; + } + + public static int Index(this TensorShape shape, int n, int c, int h, int w) + { + int index = + n * shape.Height() * shape.Width() * shape.Channels() + + c * shape.Height() * shape.Width() + + h * shape.Width() + + w; + return index; + } + } +} diff --git a/com.unity.ml-agents/Runtime/Inference/TensorExtensions.cs.meta b/com.unity.ml-agents/Runtime/Inference/TensorExtensions.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..a7715c3b412b3014951d7185368bb756336956e4 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Inference/TensorExtensions.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Inference/TensorGenerator.cs b/com.unity.ml-agents/Runtime/Inference/TensorGenerator.cs new file mode 100644 index 0000000000000000000000000000000000000000..0db6b8587ebbfb1a44605cf18c4e968436131ba7 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Inference/TensorGenerator.cs @@ -0,0 +1,179 @@ +using System.Collections.Generic; +using Unity.InferenceEngine; +using Unity.MLAgents.Sensors; + +namespace Unity.MLAgents.Inference +{ + /// + /// Mapping between Tensor names and generators. + /// A TensorGenerator implements a Dictionary of strings (node names) to an Action. + /// The Action take as argument the tensor, the current batch size and a Dictionary of + /// Agent to AgentInfo corresponding to the current batch. + /// Each Generator reshapes and fills the data of the tensor based of the data of the batch. + /// When the TensorProxy is an Input to the model, the shape of the Tensor will be modified + /// depending on the current batch size and the data of the Tensor will be filled using the + /// Dictionary of Agent to AgentInfo. + /// When the TensorProxy is an Output of the model, only the shape of the Tensor will be + /// modified using the current batch size. The data will be pre-filled with zeros. + /// + internal class TensorGenerator + { + public interface IGenerator + { + /// + /// Modifies the data inside a Tensor according to the information contained in the + /// AgentInfos contained in the current batch. + /// + /// The tensor the data and shape will be modified. + /// The number of agents present in the current batch. + /// + /// List of AgentInfos containing the information that will be used to populate + /// the tensor's data. + /// + void Generate( + TensorProxy tensorProxy, int batchSize, IList infos); + } + + readonly Dictionary m_Dict = new Dictionary(); + int m_ApiVersion; + + /// + /// Returns a new TensorGenerators object. + /// + /// The seed the Generators will be initialized with. + /// Tensor allocator. + /// Dictionary of AgentInfo.id to memory for use in the inference model. + /// + /// Inference only: set to true if the action selection from model should be + /// deterministic. + public TensorGenerator( + int seed, + Dictionary> memories, + object sentisModel = null, + bool deterministicInference = false) + { + // If model is null, no inference to run and exception is thrown before reaching here. + if (sentisModel == null) + { + return; + } + var model = (Model)sentisModel; + using var modelInfo = new SentisModelInfo(model, deterministicInference); + + m_ApiVersion = modelInfo.Version; + + // Generator for Inputs + m_Dict[TensorNames.BatchSizePlaceholder] = + new BatchSizeGenerator(); + m_Dict[TensorNames.SequenceLengthPlaceholder] = + new SequenceLengthGenerator(); + m_Dict[TensorNames.RecurrentInPlaceholder] = + new RecurrentInputGenerator(memories); + + m_Dict[TensorNames.PreviousActionPlaceholder] = + new PreviousActionInputGenerator(); + m_Dict[TensorNames.ActionMaskPlaceholder] = + new ActionMaskInputGenerator(); + m_Dict[TensorNames.RandomNormalEpsilonPlaceholder] = + new RandomNormalInputGenerator(seed); + + + // Generators for Outputs + if (modelInfo.HasContinuousOutputs) + { + m_Dict[modelInfo.ContinuousOutputName] = new BiDimensionalOutputGenerator(); + } + if (modelInfo.HasDiscreteOutputs) + { + m_Dict[modelInfo.DiscreteOutputName] = new BiDimensionalOutputGenerator(); + } + m_Dict[TensorNames.RecurrentOutput] = new BiDimensionalOutputGenerator(); + m_Dict[TensorNames.ValueEstimateOutput] = new BiDimensionalOutputGenerator(); + } + + public void InitializeObservations(List sensors) + { + if (m_ApiVersion == (int)SentisModelParamLoader.ModelApiVersion.MLAgents1_0) + { + // Loop through the sensors on a representative agent. + // All vector observations use a shared ObservationGenerator since they are concatenated. + // All other observations use a unique ObservationInputGenerator + var visIndex = 0; + ObservationGenerator vecObsGen = null; + for (var sensorIndex = 0; sensorIndex < sensors.Count; sensorIndex++) + { + var sensor = sensors[sensorIndex]; + var rank = sensor.GetObservationSpec().Rank; + ObservationGenerator obsGen = null; + string obsGenName = null; + switch (rank) + { + case 1: + if (vecObsGen == null) + { + vecObsGen = new ObservationGenerator(); + } + obsGen = vecObsGen; + obsGenName = TensorNames.VectorObservationPlaceholder; + break; + case 2: + // If the tensor is of rank 2, we use the index of the sensor + // to create the name + obsGen = new ObservationGenerator(); + obsGenName = TensorNames.GetObservationName(sensorIndex); + break; + case 3: + // If the tensor is of rank 3, we use the "visual observation + // index", which only counts the rank 3 sensors + obsGen = new ObservationGenerator(); + obsGenName = TensorNames.GetVisualObservationName(visIndex); + visIndex++; + break; + default: + throw new UnityAgentsException( + $"Sensor {sensor.GetName()} have an invalid rank {rank}"); + } + obsGen.AddSensorIndex(sensorIndex); + m_Dict[obsGenName] = obsGen; + } + } + + if (m_ApiVersion == (int)SentisModelParamLoader.ModelApiVersion.MLAgents2_0) + { + for (var sensorIndex = 0; sensorIndex < sensors.Count; sensorIndex++) + { + var obsGen = new ObservationGenerator(); + var obsGenName = TensorNames.GetObservationName(sensorIndex); + obsGen.AddSensorIndex(sensorIndex); + m_Dict[obsGenName] = obsGen; + } + } + } + + /// + /// Populates the data of the tensor inputs given the data contained in the current batch + /// of agents. + /// + /// Enumerable of tensors that will be modified. + /// The number of agents present in the current batch + /// + /// List of AgentsInfos and Sensors that contains the + /// data that will be used to modify the tensors + /// One of the tensor does not have an + /// associated generator. + public void GenerateTensors( + IReadOnlyList tensors, int currentBatchSize, IList infos) + { + for (var tensorIndex = 0; tensorIndex < tensors.Count; tensorIndex++) + { + var tensor = tensors[tensorIndex]; + if (!m_Dict.ContainsKey(tensor.name)) + { + throw new UnityAgentsException( + $"Unknown tensorProxy expected as input : {tensor.name}"); + } + m_Dict[tensor.name].Generate(tensor, currentBatchSize, infos); + } + } + } +} diff --git a/com.unity.ml-agents/Runtime/Inference/TensorGenerator.cs.meta b/com.unity.ml-agents/Runtime/Inference/TensorGenerator.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..0bfd9673a9f882a249998ab9d40a592177faba78 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Inference/TensorGenerator.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Inference/TensorNames.cs b/com.unity.ml-agents/Runtime/Inference/TensorNames.cs new file mode 100644 index 0000000000000000000000000000000000000000..48ae04b5f6e192a111ccd8f42e8c8438e8a4c04e --- /dev/null +++ b/com.unity.ml-agents/Runtime/Inference/TensorNames.cs @@ -0,0 +1,50 @@ +namespace Unity.MLAgents.Inference +{ + /// + /// Contains the names of the input and output tensors for the Inference Brain. + /// + internal static class TensorNames + { + public const string BatchSizePlaceholder = "batch_size"; + public const string SequenceLengthPlaceholder = "sequence_length"; + public const string VectorObservationPlaceholder = "vector_observation"; + public const string RecurrentInPlaceholder = "recurrent_in"; + public const string VisualObservationPlaceholderPrefix = "visual_observation_"; + public const string ObservationPlaceholderPrefix = "obs_"; + public const string PreviousActionPlaceholder = "prev_action"; + public const string ActionMaskPlaceholder = "action_masks"; + public const string RandomNormalEpsilonPlaceholder = "epsilon"; + + public const string ValueEstimateOutput = "value_estimate"; + public const string RecurrentOutput = "recurrent_out"; + public const string MemorySize = "memory_size"; + public const string VersionNumber = "version_number"; + public const string ContinuousActionOutputShape = "continuous_action_output_shape"; + public const string DiscreteActionOutputShape = "discrete_action_output_shape"; + public const string ContinuousActionOutput = "continuous_actions"; + public const string DiscreteActionOutput = "discrete_actions"; + public const string DeterministicContinuousActionOutput = "deterministic_continuous_actions"; + public const string DeterministicDiscreteActionOutput = "deterministic_discrete_actions"; + + // Deprecated TensorNames entries for backward compatibility + public const string IsContinuousControlDeprecated = "is_continuous_control"; + public const string ActionOutputDeprecated = "action"; + public const string ActionOutputShapeDeprecated = "action_output_shape"; + + /// + /// Returns the name of the visual observation with a given index + /// + public static string GetVisualObservationName(int index) + { + return VisualObservationPlaceholderPrefix + index; + } + + /// + /// Returns the name of the observation with a given index + /// + public static string GetObservationName(int index) + { + return ObservationPlaceholderPrefix + index; + } + } +} diff --git a/com.unity.ml-agents/Runtime/Inference/TensorNames.cs.meta b/com.unity.ml-agents/Runtime/Inference/TensorNames.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..5c8c6b80b100d90c9d34f943e95751e6788d02d5 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Inference/TensorNames.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Inference/TensorProxy.cs b/com.unity.ml-agents/Runtime/Inference/TensorProxy.cs new file mode 100644 index 0000000000000000000000000000000000000000..4281ef68796b29eb81c3aba0aac1aa750f3d0e26 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Inference/TensorProxy.cs @@ -0,0 +1,205 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Unity.InferenceEngine; +using Unity.MLAgents.Inference.Utils; +using Unity.MLAgents.Policies; + +namespace Unity.MLAgents.Inference +{ + /// + /// Tensor - A class to encapsulate a Tensor used for inference. + /// + /// This class contains the Array that holds the data array, the shapes, type and the + /// placeholder in the execution graph. All the fields are editable in the inspector, + /// allowing the user to specify everything but the data in a graphical way. + /// + [Serializable] + internal class TensorProxy + { + public enum TensorType + { + Integer, + FloatingPoint + }; + + static readonly Dictionary k_TypeMap = + new Dictionary() + { + { TensorType.FloatingPoint, typeof(float) }, + { TensorType.Integer, typeof(int) } + }; + + static readonly Dictionary k_DTypeMap = + new Dictionary() + { + { TensorType.FloatingPoint, InferenceEngine.DataType.Float }, + { TensorType.Integer, InferenceEngine.DataType.Int } + }; + + public string name; + public TensorType valueType; + + // Since Type is not serializable, we use the DisplayType for the Inspector + public Type DataType => k_TypeMap[valueType]; + public DataType DType => k_DTypeMap[valueType]; + public int[] shape; + [NonSerialized] + public Tensor data; + public BackendType Device => data.dataOnBackend.backendType; + + public long Height + { + get { return shape.Length >= 4 ? shape[^2] : 1; } + } + + public long Width + { + get { return shape.Length >= 3 ? shape[^1] : 1; } + } + + public long Channels + { + get + { + return shape.Length >= 4 ? shape[^3] : + shape.Length == 3 ? shape[^2] : + shape.Length == 2 ? shape[^1] : 1; + } + } + + ~TensorProxy() + { + Dispose(); + } + + void Dispose() + { + if (data.dataOnBackend.backendType != BackendType.CPU) + { + data?.Dispose(); + } + } + } + + internal static class TensorUtils + { + public static void ResizeTensor(TensorProxy tensor, int batch) + { + if (tensor.shape[0] == batch && + tensor.data != null && tensor.data.Batch() == batch) + { + return; + } + + tensor.data?.Dispose(); + tensor.shape[0] = batch; + var newTensorShape = new TensorShape(tensor.shape.Select(i => (int)i).ToArray()); + tensor.data = CreateEmptyTensor(newTensorShape, tensor.DType); + } + + public static Tensor CreateEmptyTensor(TensorShape shape, DataType dataType) + { + Tensor tensor = null; + switch (dataType) + { + case DataType.Float: + tensor = new Tensor(shape); + break; + case DataType.Int: + tensor = new Tensor(shape); + break; + } + + return tensor; + } + + internal static int[] TensorShapeFromSentis(TensorShape src) + { + if (src.rank == 2) + { + return new int[] { src.Batch(), src.Channels() }; + } + + if (src.Height() == 1 && src.Width() == 1) + { + return new int[] { src.Batch(), src.Channels() }; + } + + return new int[] { src.Batch(), src.Channels(), src.Height(), src.Width() }; + } + + public static TensorProxy TensorProxyFromSentis(Tensor src, string nameOverride = null) + { + var shape = TensorShapeFromSentis(src.shape); + return new TensorProxy + { + // name = nameOverride ?? src.name, + name = nameOverride ?? "", + valueType = src.dataType == DataType.Float + ? TensorProxy.TensorType.FloatingPoint + : TensorProxy.TensorType.Integer, + shape = shape, + data = src + }; + } + + /// + /// Fill a specific batch of a TensorProxy with a given value + /// + /// + /// The batch index to fill. + /// + public static void FillTensorBatch(TensorProxy tensorProxy, int batch, float fillValue) + { + var height = tensorProxy.data.Height(); + var width = tensorProxy.data.Width(); + var channels = tensorProxy.data.Channels(); + + tensorProxy.data.CompleteAllPendingOperations(); + + for (var h = 0; h < height; h++) + { + for (var w = 0; w < width; w++) + { + for (var c = 0; c < channels; c++) + { + ((Tensor)tensorProxy.data)[batch, c, h, w] = fillValue; + } + } + } + } + + /// + /// Fill a pre-allocated Tensor with random numbers + /// + /// The pre-allocated Tensor to fill + /// RandomNormal object used to populate tensor + /// + /// Throws when trying to fill a Tensor of type other than float + /// + /// + /// Throws when the Tensor is not allocated + /// + public static void FillTensorWithRandomNormal( + TensorProxy tensorProxy, RandomNormal randomNormal) + { + if (tensorProxy.DataType != typeof(float)) + { + throw new NotImplementedException("Only float data types are currently supported"); + } + + if (tensorProxy.data == null) + { + throw new ArgumentNullException(); + } + + tensorProxy.data.CompleteAllPendingOperations(); + + for (var i = 0; i < tensorProxy.data.Length(); i++) + { + ((Tensor)tensorProxy.data)[i] = (float)randomNormal.NextDouble(); + } + } + } +} diff --git a/com.unity.ml-agents/Runtime/Inference/TensorProxy.cs.meta b/com.unity.ml-agents/Runtime/Inference/TensorProxy.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..5e0dd9d08d2c4b29a3777b243787a97e140ec414 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Inference/TensorProxy.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Inference/Utils.meta b/com.unity.ml-agents/Runtime/Inference/Utils.meta new file mode 100644 index 0000000000000000000000000000000000000000..5431b3d6468328200572f93c093a8083cdcd75b1 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Inference/Utils.meta differ diff --git a/com.unity.ml-agents/Runtime/Inference/Utils/Multinomial.cs b/com.unity.ml-agents/Runtime/Inference/Utils/Multinomial.cs new file mode 100644 index 0000000000000000000000000000000000000000..41603dd3baf718e931897815e40d52866ff8833b --- /dev/null +++ b/com.unity.ml-agents/Runtime/Inference/Utils/Multinomial.cs @@ -0,0 +1,59 @@ +namespace Unity.MLAgents.Inference.Utils +{ + /// + /// Multinomial - Draws samples from a multinomial distribution given a (potentially unscaled) + /// cumulative mass function (CMF). This means that the CMF need not "end" with probability + /// mass of 1.0. For instance: [0.1, 0.2, 0.5] is a valid (unscaled). What is important is + /// that it is a cumulative function, not a probability function. In other words, + /// entry[i] = P(x \le i), NOT P(i - 1 \le x \lt i). + /// (\le stands for less than or equal to while \lt is strictly less than). + /// + internal class Multinomial + { + readonly System.Random m_Random; + + /// + /// Constructor. + /// + /// + /// Seed for the random number generator used in the sampling process. + /// + public Multinomial(int seed) + { + m_Random = new System.Random(seed); + } + + /// + /// Samples from the Multinomial distribution defined by the provided cumulative + /// mass function. + /// + /// + /// Cumulative mass function, which may be unscaled. The entries in this array need + /// to be monotonic (always increasing). If the CMF is scaled, then the last entry in + /// the array will be 1.0. + /// + /// The number of possible branches, i.e. the effective size of the cmf array. + /// A sampled index from the CMF ranging from 0 to branchSize-1. + public int Sample(float[] cmf, int branchSize) + { + var p = (float)m_Random.NextDouble() * cmf[branchSize - 1]; + var cls = 0; + while (cmf[cls] < p) + { + ++cls; + } + + return cls; + } + + /// + /// Samples from the Multinomial distribution defined by the provided cumulative + /// mass function. + /// + /// A sampled index from the CMF ranging from 0 to cmf.Length-1. + public int Sample(float[] cmf) + { + return Sample(cmf, cmf.Length); + } + } +} diff --git a/com.unity.ml-agents/Runtime/Inference/Utils/Multinomial.cs.meta b/com.unity.ml-agents/Runtime/Inference/Utils/Multinomial.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..2467eb50b3356986b13b461f52b5fe0efda2ea74 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Inference/Utils/Multinomial.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Inference/Utils/RandomNormal.cs b/com.unity.ml-agents/Runtime/Inference/Utils/RandomNormal.cs new file mode 100644 index 0000000000000000000000000000000000000000..4b0e1e7e2fa230f8cdf610065c5a2b437af0933c --- /dev/null +++ b/com.unity.ml-agents/Runtime/Inference/Utils/RandomNormal.cs @@ -0,0 +1,56 @@ +using System; + +namespace Unity.MLAgents.Inference.Utils +{ + /// + /// RandomNormal - A random number generator that produces normally distributed random + /// numbers using the Marsaglia polar method: + /// https://en.wikipedia.org/wiki/Marsaglia_polar_method + /// TODO: worth overriding System.Random instead of aggregating? + /// + internal class RandomNormal + { + readonly double m_Mean; + readonly double m_Stddev; + readonly Random m_Random; + + public RandomNormal(int seed, float mean = 0.0f, float stddev = 1.0f) + { + m_Mean = mean; + m_Stddev = stddev; + m_Random = new Random(seed); + } + + // Each iteration produces two numbers. Hold one here for next call + bool m_HasSpare; + double m_SpareUnscaled; + + /// + /// Return the next random double number. + /// + /// Next random double number. + public double NextDouble() + { + if (m_HasSpare) + { + m_HasSpare = false; + return m_SpareUnscaled * m_Stddev + m_Mean; + } + + double u, v, s; + do + { + u = m_Random.NextDouble() * 2.0 - 1.0; + v = m_Random.NextDouble() * 2.0 - 1.0; + s = u * u + v * v; + } + while (s >= 1.0 || Math.Abs(s) < double.Epsilon); + + s = Math.Sqrt(-2.0 * Math.Log(s) / s); + m_SpareUnscaled = u * s; + m_HasSpare = true; + + return v * s * m_Stddev + m_Mean; + } + } +} diff --git a/com.unity.ml-agents/Runtime/Inference/Utils/RandomNormal.cs.meta b/com.unity.ml-agents/Runtime/Inference/Utils/RandomNormal.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..3cd152462dc785134ddb0915fe3602b0e08e8c20 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Inference/Utils/RandomNormal.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/InplaceArray.cs b/com.unity.ml-agents/Runtime/InplaceArray.cs new file mode 100644 index 0000000000000000000000000000000000000000..b5472cf010d5ac9033edf3c50c0ff273d716819f --- /dev/null +++ b/com.unity.ml-agents/Runtime/InplaceArray.cs @@ -0,0 +1,240 @@ +using System; +using System.Collections.Generic; + +namespace Unity.MLAgents +{ + /// + /// An array-like object that stores up to four elements. + /// This is a value type that does not allocate any additional memory. + /// + /// + /// This does not implement any interfaces such as IList, in order to avoid any accidental boxing allocations. + /// + /// T + public struct InplaceArray : IEquatable> where T : struct + { + private const int k_MaxLength = 4; + private readonly int m_Length; + + private T m_Elem0; + private T m_Elem1; + private T m_Elem2; + private T m_Elem3; + + /// + /// Create a length-1 array. + /// + /// Length of axis 0. + public InplaceArray(T elem0) + { + m_Length = 1; + m_Elem0 = elem0; + m_Elem1 = new T(); + m_Elem2 = new T(); + m_Elem3 = new T(); + } + + /// + /// Create a length-2 array. + /// + /// Length of axis 0. + /// Length of axis 1. + public InplaceArray(T elem0, T elem1) + { + m_Length = 2; + m_Elem0 = elem0; + m_Elem1 = elem1; + m_Elem2 = new T(); + m_Elem3 = new T(); + } + + /// + /// Create a length-3 array. + /// + /// Length of axis 0. + /// Length of axis 1. + /// Length of axis 2. + public InplaceArray(T elem0, T elem1, T elem2) + { + m_Length = 3; + m_Elem0 = elem0; + m_Elem1 = elem1; + m_Elem2 = elem2; + m_Elem3 = new T(); + } + + /// + /// Create a length-3 array. + /// + /// Length of axis 0. + /// Length of axis 1. + /// Length of axis 2. + /// Length of axis 3. + public InplaceArray(T elem0, T elem1, T elem2, T elem3) + { + m_Length = 4; + m_Elem0 = elem0; + m_Elem1 = elem1; + m_Elem2 = elem2; + m_Elem3 = elem3; + } + + /// + /// Construct an InplaceArray from an IList (e.g. Array or List). + /// The source must be non-empty and have at most 4 elements. + /// + /// The `IList` to construct the array from. + /// Corresponding `InplaceArray` from the input IList. + /// Argument out of range + public static InplaceArray FromList(IList elems) + { + switch (elems.Count) + { + case 1: + return new InplaceArray(elems[0]); + case 2: + return new InplaceArray(elems[0], elems[1]); + case 3: + return new InplaceArray(elems[0], elems[1], elems[2]); + case 4: + return new InplaceArray(elems[0], elems[1], elems[2], elems[3]); + default: + throw new ArgumentOutOfRangeException(); + } + } + + /// + /// Per-element access. + /// + /// The index to get or set. + /// Index out of range + public T this[int index] + { + get + { + if (index >= Length) + { + throw new IndexOutOfRangeException(); + } + + switch (index) + { + case 0: + return m_Elem0; + case 1: + return m_Elem1; + case 2: + return m_Elem2; + case 3: + return m_Elem3; + default: + throw new IndexOutOfRangeException(); + } + } + + set + { + if (index >= Length) + { + throw new IndexOutOfRangeException(); + } + + switch (index) + { + case 0: + m_Elem0 = value; + break; + case 1: + m_Elem1 = value; + break; + case 2: + m_Elem2 = value; + break; + case 3: + m_Elem3 = value; + break; + default: + throw new IndexOutOfRangeException(); + } + } + } + + /// + /// The length of the array. + /// + public int Length + { + get => m_Length; + } + + /// + /// Returns a string representation of the array's elements. + /// + /// The string summary of the `InplaceArray`. + /// Index out of range + public override string ToString() + { + switch (m_Length) + { + case 1: + return $"[{m_Elem0}]"; + case 2: + return $"[{m_Elem0}, {m_Elem1}]"; + case 3: + return $"[{m_Elem0}, {m_Elem1}, {m_Elem2}]"; + case 4: + return $"[{m_Elem0}, {m_Elem1}, {m_Elem2}, {m_Elem3}]"; + default: + throw new IndexOutOfRangeException(); + } + } + + /// + /// Check that the arrays have the same length and have all equal values. + /// + /// The first 'InplaceArray' to compare. + /// The second 'InplaceArray' to compare. + /// Whether the arrays are equivalent. + public static bool operator ==(InplaceArray lhs, InplaceArray rhs) + { + return lhs.Equals(rhs); + } + + /// + /// Check that the arrays are not equivalent. + /// + /// The first 'InplaceArray' to compare. + /// The second 'InplaceArray' to compare. + /// Whether the arrays are not equivalent + public static bool operator !=(InplaceArray lhs, InplaceArray rhs) => !lhs.Equals(rhs); + + /// + /// Check that the arrays are equivalent. + /// + /// The other 'InplaceArray' to compare. + /// Whether the arrays are not equivalent + public override bool Equals(object other) => other is InplaceArray other1 && this.Equals(other1); + + /// + /// Check that the arrays are equivalent. + /// + /// The other 'InplaceArray' to compare. + /// Whether the arrays are not equivalent + public bool Equals(InplaceArray other) + { + // See https://montemagno.com/optimizing-c-struct-equality-with-iequatable/ + var thisTuple = (m_Elem0, m_Elem1, m_Elem2, m_Elem3, Length); + var otherTuple = (other.m_Elem0, other.m_Elem1, other.m_Elem2, other.m_Elem3, other.Length); + return thisTuple.Equals(otherTuple); + } + + /// + /// Get a hashcode for the array. + /// + /// The hashcode of the `InplaceArray`. + public override int GetHashCode() + { + return (m_Elem0, m_Elem1, m_Elem2, m_Elem3, Length).GetHashCode(); + } + } +} diff --git a/com.unity.ml-agents/Runtime/InplaceArray.cs.meta b/com.unity.ml-agents/Runtime/InplaceArray.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..3e4ab0c9287cbbfb41ce9d089b3c35eb3494cae1 Binary files /dev/null and b/com.unity.ml-agents/Runtime/InplaceArray.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Input.meta b/com.unity.ml-agents/Runtime/Input.meta new file mode 100644 index 0000000000000000000000000000000000000000..2e063c20aabd40af2a9b68efdf1210e3405e3803 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Input.meta differ diff --git a/com.unity.ml-agents/Runtime/Input/Adaptors.meta b/com.unity.ml-agents/Runtime/Input/Adaptors.meta new file mode 100644 index 0000000000000000000000000000000000000000..f75266d5b71ec4708c923ff44854743f204e6269 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Input/Adaptors.meta differ diff --git a/com.unity.ml-agents/Runtime/Input/Adaptors/ButtonInputActionAdaptor.cs b/com.unity.ml-agents/Runtime/Input/Adaptors/ButtonInputActionAdaptor.cs new file mode 100644 index 0000000000000000000000000000000000000000..a3ba2206bbd8ab87711221b50f703d7dc6d3581b --- /dev/null +++ b/com.unity.ml-agents/Runtime/Input/Adaptors/ButtonInputActionAdaptor.cs @@ -0,0 +1,46 @@ +#if MLA_INPUT_SYSTEM +using Unity.MLAgents.Actuators; +using UnityEngine.InputSystem; +using UnityEngine.InputSystem.Controls; +using UnityEngine.InputSystem.LowLevel; + +namespace Unity.MLAgents.Input +{ + /// + /// Class that translates data between the a and + /// the ML-Agents object. + /// + [UnityEngine.Scripting.APIUpdating.MovedFrom("Unity.MLAgents.Extensions.Input")] + public class ButtonInputActionAdaptor : IRLActionInputAdaptor + { + /// + /// TODO this method needs to be more nuanced depending the types of controls that can back it. i.e. TriggerControls + /// are continuous buttons, etc. + /// Currently returns an with 1 branch of size 2. One value for not pressed, and one + /// for pressed. + /// + /// The action associated with this adaptor to help determine the action space. + /// ActionSpec with 1 branch of size 2. + public ActionSpec GetActionSpecForInputAction(InputAction action) + { + return ActionSpec.MakeDiscrete(2); + } + + /// TODO again this might need to be more nuanced for things like continuous buttons. + /// + public void WriteToInputEventForAction(InputEventPtr eventPtr, InputAction action, InputControl control, ActionSpec actionSpec, in ActionBuffers actionBuffers) + { + var val = actionBuffers.DiscreteActions[0]; + ((ButtonControl)control).WriteValueIntoEvent((float)val, eventPtr); + } + + /// > + public void WriteToHeuristic(InputAction action, in ActionBuffers actionBuffers) + { + var discreteActions = actionBuffers.DiscreteActions; + var val = action.ReadValue(); + discreteActions[0] = (int)val; + } + } +} +#endif // MLA_INPUT_SYSTEM diff --git a/com.unity.ml-agents/Runtime/Input/Adaptors/ButtonInputActionAdaptor.cs.meta b/com.unity.ml-agents/Runtime/Input/Adaptors/ButtonInputActionAdaptor.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..68f92bc212e4c214c3ca06d7fd6fb64782505907 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Input/Adaptors/ButtonInputActionAdaptor.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Input/Adaptors/DoubleInputActionAdaptor.cs b/com.unity.ml-agents/Runtime/Input/Adaptors/DoubleInputActionAdaptor.cs new file mode 100644 index 0000000000000000000000000000000000000000..d84585051731034749b08593047d43269e970121 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Input/Adaptors/DoubleInputActionAdaptor.cs @@ -0,0 +1,37 @@ +#if MLA_INPUT_SYSTEM +using Unity.MLAgents.Actuators; +using UnityEngine.InputSystem; +using UnityEngine.InputSystem.Controls; +using UnityEngine.InputSystem.LowLevel; + +namespace Unity.MLAgents.Input +{ + /// + /// Translates data from a . + /// + [UnityEngine.Scripting.APIUpdating.MovedFrom("Unity.MLAgents.Extensions.Input")] + public class DoubleInputActionAdaptor : IRLActionInputAdaptor + { + /// + public ActionSpec GetActionSpecForInputAction(InputAction action) + { + return ActionSpec.MakeContinuous(1); + } + + /// + public void WriteToInputEventForAction(InputEventPtr eventPtr, InputAction action, InputControl control, ActionSpec actionSpec, in ActionBuffers actionBuffers) + { + var val = actionBuffers.ContinuousActions[0]; + ((DoubleControl)control).WriteValueIntoEvent((double)val, eventPtr); + } + + /// + public void WriteToHeuristic(InputAction action, in ActionBuffers actionBuffers) + { + var actions = actionBuffers.ContinuousActions; + var val = (float)action.ReadValue(); + actions[0] = val; + } + } +} +#endif // MLA_INPUT_SYSTEM diff --git a/com.unity.ml-agents/Runtime/Input/Adaptors/DoubleInputActionAdaptor.cs.meta b/com.unity.ml-agents/Runtime/Input/Adaptors/DoubleInputActionAdaptor.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..ced0139369e5b806179b9e5c79cac50cf7260863 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Input/Adaptors/DoubleInputActionAdaptor.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Input/Adaptors/FloatInputActionAdaptor.cs b/com.unity.ml-agents/Runtime/Input/Adaptors/FloatInputActionAdaptor.cs new file mode 100644 index 0000000000000000000000000000000000000000..32b9158aeb2628ac88bf3615a3fd55575bfd0eaf --- /dev/null +++ b/com.unity.ml-agents/Runtime/Input/Adaptors/FloatInputActionAdaptor.cs @@ -0,0 +1,36 @@ +#if MLA_INPUT_SYSTEM +using Unity.MLAgents.Actuators; +using UnityEngine.InputSystem; +using UnityEngine.InputSystem.LowLevel; + +namespace Unity.MLAgents.Input +{ + /// + /// Translates data from any control that extends from . + /// + [UnityEngine.Scripting.APIUpdating.MovedFrom("Unity.MLAgents.Extensions.Input")] + public class FloatInputActionAdaptor : IRLActionInputAdaptor + { + /// + public ActionSpec GetActionSpecForInputAction(InputAction action) + { + return ActionSpec.MakeContinuous(1); + } + + /// + public void WriteToInputEventForAction(InputEventPtr eventPtr, InputAction action, InputControl control, ActionSpec actionSpec, in ActionBuffers actionBuffers) + { + var val = actionBuffers.ContinuousActions[0]; + control.WriteValueIntoEvent(val, eventPtr); + } + + /// + public void WriteToHeuristic(InputAction action, in ActionBuffers actionBuffers) + { + var actions = actionBuffers.ContinuousActions; + var val = action.ReadValue(); + actions[0] = val; + } + } +} +#endif // MLA_INPUT_SYSTEM diff --git a/com.unity.ml-agents/Runtime/Input/Adaptors/FloatInputActionAdaptor.cs.meta b/com.unity.ml-agents/Runtime/Input/Adaptors/FloatInputActionAdaptor.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..9e12bca95fd8453dd7d9690dfd1fb1201f88c532 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Input/Adaptors/FloatInputActionAdaptor.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Input/Adaptors/IntegerInputActionAdaptor.cs b/com.unity.ml-agents/Runtime/Input/Adaptors/IntegerInputActionAdaptor.cs new file mode 100644 index 0000000000000000000000000000000000000000..75e60ea9d524a745d7bb1aac2bec6d5f1f5d32f2 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Input/Adaptors/IntegerInputActionAdaptor.cs @@ -0,0 +1,37 @@ +#if MLA_INPUT_SYSTEM +using Unity.MLAgents.Actuators; +using UnityEngine.InputSystem; +using UnityEngine.InputSystem.LowLevel; + +namespace Unity.MLAgents.Input +{ + /// + /// Translates data from a . + /// + [UnityEngine.Scripting.APIUpdating.MovedFrom("Unity.MLAgents.Extensions.Input")] + public class IntegerInputActionAdaptor : IRLActionInputAdaptor + { + // TODO need to figure out how we can infer the branch size from here. + /// + public ActionSpec GetActionSpecForInputAction(InputAction action) + { + return ActionSpec.MakeDiscrete(2); + } + + /// + public void WriteToInputEventForAction(InputEventPtr eventPtr, InputAction action, InputControl control, ActionSpec actionSpec, in ActionBuffers actionBuffers) + { + var val = actionBuffers.DiscreteActions[0]; + control.WriteValueIntoEvent(val, eventPtr); + } + + /// + public void WriteToHeuristic(InputAction action, in ActionBuffers actionBuffers) + { + var actions = actionBuffers.DiscreteActions; + var val = action.ReadValue(); + actions[0] = val; + } + } +} +#endif // MLA_INPUT_SYSTEM diff --git a/com.unity.ml-agents/Runtime/Input/Adaptors/IntegerInputActionAdaptor.cs.meta b/com.unity.ml-agents/Runtime/Input/Adaptors/IntegerInputActionAdaptor.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..3db5316f100844fc3200568d8c3eeb22ffb03fae Binary files /dev/null and b/com.unity.ml-agents/Runtime/Input/Adaptors/IntegerInputActionAdaptor.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Input/Adaptors/Vector2InputActionAdaptor.cs b/com.unity.ml-agents/Runtime/Input/Adaptors/Vector2InputActionAdaptor.cs new file mode 100644 index 0000000000000000000000000000000000000000..72a8e91fe55ce01d0266335952d87385a9ac3af9 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Input/Adaptors/Vector2InputActionAdaptor.cs @@ -0,0 +1,43 @@ +#if MLA_INPUT_SYSTEM +using Unity.MLAgents.Actuators; +using UnityEngine; +using UnityEngine.InputSystem; +using UnityEngine.InputSystem.LowLevel; + +namespace Unity.MLAgents.Input +{ + /// + /// Translates data from any control that extends from . + /// + [UnityEngine.Scripting.APIUpdating.MovedFrom("Unity.MLAgents.Extensions.Input")] + public class Vector2InputActionAdaptor : IRLActionInputAdaptor + { + /// + public ActionSpec GetActionSpecForInputAction(InputAction action) + { + // TODO create the action spec based on what controls back the action + return ActionSpec.MakeContinuous(2); + } + + /// + public void WriteToInputEventForAction(InputEventPtr eventPtr, InputAction action, + InputControl control, + ActionSpec actionSpec, + in ActionBuffers actionBuffers) + { + var x = actionBuffers.ContinuousActions[0]; + var y = actionBuffers.ContinuousActions[1]; + control.WriteValueIntoEvent(new Vector2(x, y), eventPtr); + } + + /// + public void WriteToHeuristic(InputAction action, in ActionBuffers actionBuffers) + { + var value = action.ReadValue(); + var continuousActions = actionBuffers.ContinuousActions; + continuousActions[0] = value.x; + continuousActions[1] = value.y; + } + } +} +#endif // MLA_INPUT_SYSTEM diff --git a/com.unity.ml-agents/Runtime/Input/Adaptors/Vector2InputActionAdaptor.cs.meta b/com.unity.ml-agents/Runtime/Input/Adaptors/Vector2InputActionAdaptor.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..7f87c8fefd78603106daec6b65f34a720bab4519 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Input/Adaptors/Vector2InputActionAdaptor.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Input/AssemblyInfo.cs b/com.unity.ml-agents/Runtime/Input/AssemblyInfo.cs new file mode 100644 index 0000000000000000000000000000000000000000..311008db413bcd6bddbb65204b58e76be3fd9882 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Input/AssemblyInfo.cs @@ -0,0 +1,4 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Unity.ML-Agents.Runtime.Tests")] +[assembly: InternalsVisibleTo("Unity.ML-Agents.Runtime.Input.Tests")] diff --git a/com.unity.ml-agents/Runtime/Input/AssemblyInfo.cs.meta b/com.unity.ml-agents/Runtime/Input/AssemblyInfo.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..93ccdcd83334f0d2173446fb083997c5bc4f4400 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Input/AssemblyInfo.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Input/IInputActionAssetProvider.cs b/com.unity.ml-agents/Runtime/Input/IInputActionAssetProvider.cs new file mode 100644 index 0000000000000000000000000000000000000000..68bc2dbc71b3abeb76223d81814d1a47715f0c9b --- /dev/null +++ b/com.unity.ml-agents/Runtime/Input/IInputActionAssetProvider.cs @@ -0,0 +1,28 @@ +#if MLA_INPUT_SYSTEM +using UnityEngine.InputSystem; + +namespace Unity.MLAgents.Input +{ + /// + /// Implement this interface if you are listening to C# events from the generated C# class from the + /// . This interface works with the in order + /// to allow ML-Agents to simulate input actions based on the instance of the + /// used to listen to events. If you implement this interface the will use + /// what is returned from as the asset to base it's simulated input for. + /// Otherwise, the will look for the component + /// and use the asset from there. If you have multiple components handling PlayerInput on the same GameObject + /// they will need to share the same instance of the in order to get the simulated + /// input. + /// + [UnityEngine.Scripting.APIUpdating.MovedFrom("Unity.MLAgents.Extensions.Input")] + public interface IInputActionAssetProvider + { + /// + /// Returns the instance being from the generated C# class of the + /// in order to correctly fire events when simulating input from ML-Agents. + /// + /// The instance of the you are listening for events on. + (InputActionAsset, IInputActionCollection2) GetInputActionAsset(); + } +} +#endif // MLA_INPUT_SYSTEM diff --git a/com.unity.ml-agents/Runtime/Input/IInputActionAssetProvider.cs.meta b/com.unity.ml-agents/Runtime/Input/IInputActionAssetProvider.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..8b441de4dcfb95b92f28afca014445850c4923fa Binary files /dev/null and b/com.unity.ml-agents/Runtime/Input/IInputActionAssetProvider.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Input/IRLActionInputAdaptor.cs b/com.unity.ml-agents/Runtime/Input/IRLActionInputAdaptor.cs new file mode 100644 index 0000000000000000000000000000000000000000..b6aa0d8af5ff4d3a425de515c735b33bcc80bd41 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Input/IRLActionInputAdaptor.cs @@ -0,0 +1,41 @@ +#if MLA_INPUT_SYSTEM +using Unity.MLAgents.Actuators; +using UnityEngine.InputSystem; +using UnityEngine.InputSystem.LowLevel; + +namespace Unity.MLAgents.Input +{ + /// + /// Implement this interface in order to customize how information is translated s + /// and . + /// + [UnityEngine.Scripting.APIUpdating.MovedFrom("Unity.MLAgents.Extensions.Input")] + public interface IRLActionInputAdaptor + { + /// + /// Generate an for a given action which determines how data is translated between + /// the and ML-Agents. + /// + /// The to based the from. + /// An instance based off the information in the . + ActionSpec GetActionSpecForInputAction(InputAction action); + + /// + /// Translates data from the object to the . + /// + /// The Event pointer to write to. + /// The action associated with this adaptor. + /// The control which will write the event to the . + /// The associated with this action and adaptor pair. + /// The object to read from. + void WriteToInputEventForAction(InputEventPtr eventPtr, InputAction action, InputControl control, ActionSpec actionSpec, in ActionBuffers actionBuffers); + + /// + /// Writes data from the to the . + /// + /// The to read data from. + /// The object to write data to. + void WriteToHeuristic(InputAction action, in ActionBuffers actionBuffers); + } +} +#endif // MLA_INPUT_SYSTEM diff --git a/com.unity.ml-agents/Runtime/Input/IRLActionInputAdaptor.cs.meta b/com.unity.ml-agents/Runtime/Input/IRLActionInputAdaptor.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..f59800ce366ba1b860f5088311a406747240733d Binary files /dev/null and b/com.unity.ml-agents/Runtime/Input/IRLActionInputAdaptor.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Input/InputActionActuator.cs b/com.unity.ml-agents/Runtime/Input/InputActionActuator.cs new file mode 100644 index 0000000000000000000000000000000000000000..12be1003ce746f7d68ac6d2da659fcfcfe02d61b --- /dev/null +++ b/com.unity.ml-agents/Runtime/Input/InputActionActuator.cs @@ -0,0 +1,102 @@ +#if MLA_INPUT_SYSTEM + +using Unity.MLAgents.Actuators; +using Unity.MLAgents.Policies; +using UnityEngine.InputSystem; +using UnityEngine.Profiling; + +namespace Unity.MLAgents.Input +{ + /// + /// This implementation of will send events from the ML-Agents training process, or from + /// neural networks to the via the interface. If an + /// 's indicate that the Agent is running in Heuristic Mode, + /// this Actuator will write actions from the to the object. + /// + [UnityEngine.Scripting.APIUpdating.MovedFrom("Unity.MLAgents.Extensions.Input")] + public class InputActionActuator : IActuator, IBuiltInActuator + { + readonly BehaviorParameters m_BehaviorParameters; + readonly InputAction m_Action; + readonly IRLActionInputAdaptor m_InputAdaptor; + InputActuatorEventContext m_InputActuatorEventContext; + InputDevice m_Device; + InputControl m_Control; + + /// + /// Construct an with the of the + /// component, the relevant , and the relevant + /// to convert between ml-agents <--> . + /// + /// The input device this action is bound to. + /// Used to determine if the is running in + /// heuristic mode. + /// The this we read/write data to/from + /// via the . + /// The that will convert data between ML-Agents + /// and the . + /// The object that will provide the event ptr to write to. + public InputActionActuator(InputDevice inputDevice, BehaviorParameters behaviorParameters, + InputAction action, + IRLActionInputAdaptor adaptor, + InputActuatorEventContext inputActuatorEventContext) + { + m_BehaviorParameters = behaviorParameters; + Name = $"InputActionActuator-{action.name}"; + m_Action = action; + m_InputAdaptor = adaptor; + m_InputActuatorEventContext = inputActuatorEventContext; + ActionSpec = adaptor.GetActionSpecForInputAction(m_Action); + m_Device = inputDevice; + m_Control = m_Device?.GetChildControl(m_Action.name); + } + + /// + public void OnActionReceived(ActionBuffers actionBuffers) + { + Profiler.BeginSample("InputActionActuator.OnActionReceived"); + if (!m_BehaviorParameters.IsInHeuristicMode()) + { + using (m_InputActuatorEventContext.GetEventForFrame(out var eventPtr)) + { + m_InputAdaptor.WriteToInputEventForAction(eventPtr, m_Action, m_Control, ActionSpec, actionBuffers); + } + } + Profiler.EndSample(); + } + + /// + public void WriteDiscreteActionMask(IDiscreteActionMask actionMask) + { + // TODO configure mask from editor UI? + } + + /// + public ActionSpec ActionSpec { get; } + + /// + public string Name { get; } + + /// + public void ResetData() + { + // do nothing for now + } + + /// + public void Heuristic(in ActionBuffers actionBuffersOut) + { + Profiler.BeginSample("InputActionActuator.Heuristic"); + m_InputAdaptor.WriteToHeuristic(m_Action, actionBuffersOut); + Profiler.EndSample(); + } + + /// + public BuiltInActuatorType GetBuiltInActuatorType() + { + return BuiltInActuatorType.InputActionActuator; + } + } +} + +#endif // MLA_INPUT_SYSTEM diff --git a/com.unity.ml-agents/Runtime/Input/InputActionActuator.cs.meta b/com.unity.ml-agents/Runtime/Input/InputActionActuator.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..158711cbab72c0c893d70e331309728b5e48fe77 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Input/InputActionActuator.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Input/InputActuatorComponent.cs b/com.unity.ml-agents/Runtime/Input/InputActuatorComponent.cs new file mode 100644 index 0000000000000000000000000000000000000000..a07e70a3115d5a6276956a8c30b26e6610f3a310 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Input/InputActuatorComponent.cs @@ -0,0 +1,376 @@ +#if MLA_INPUT_SYSTEM +using System; +using System.Collections.Generic; +using Unity.Collections; +using Unity.MLAgents.Actuators; +using Unity.MLAgents.Policies; +using UnityEngine; +using UnityEngine.Assertions; +using UnityEngine.InputSystem; +using UnityEngine.InputSystem.Controls; +using UnityEngine.InputSystem.LowLevel; +using UnityEngine.InputSystem.Layouts; +using UnityEngine.InputSystem.Utilities; +#if UNITY_EDITOR +using UnityEditor; +#endif + +namespace Unity.MLAgents.Input +{ + /// + /// Component class that handles the parsing of the and translates that into + /// s. + /// + [RequireComponent(typeof(PlayerInput), typeof(IInputActionAssetProvider))] + [AddComponentMenu("ML Agents/Input Actuator", (int)MenuGroup.Actuators)] + [UnityEngine.Scripting.APIUpdating.MovedFrom("Unity.MLAgents.Extensions.Input")] + public class InputActuatorComponent : ActuatorComponent + { + InputActionAsset m_InputAsset; + IInputActionCollection2 m_AssetCollection; + PlayerInput m_PlayerInput; + BehaviorParameters m_BehaviorParameters; + IActuator[] m_Actuators; + InputDevice m_Device; + + /// + /// Mapping of types to types of concrete classes. + /// + public static readonly Dictionary controlTypeToAdaptorType = new Dictionary + { + { typeof(Vector2Control), typeof(Vector2InputActionAdaptor) }, + { typeof(ButtonControl), typeof(ButtonInputActionAdaptor) }, + { typeof(IntegerControl), typeof(IntegerInputActionAdaptor) }, + { typeof(AxisControl), typeof(FloatInputActionAdaptor) }, + { typeof(DoubleControl), typeof(DoubleInputActionAdaptor) } + }; + + string m_LayoutName; + [SerializeField] + ActionSpec m_ActionSpec; + InputControlScheme m_ControlScheme; + + public const string mlAgentsLayoutFormat = "MLAT"; + public const string mlAgentsLayoutName = "MLAgentsLayout"; + public const string mlAgentsControlSchemeName = "ml-agents"; + + /// + public override ActionSpec ActionSpec + { + get + { +#if UNITY_EDITOR + if (!EditorApplication.isPlaying && m_ActionSpec.NumContinuousActions == 0 + && m_ActionSpec.BranchSizes == null + || m_ActionSpec.BranchSizes.Length == 0) + { + FindNeededComponents(); + var actuators = CreateActuatorsFromMap(m_InputAsset.FindActionMap(m_PlayerInput.defaultActionMap), + m_BehaviorParameters, + null, + InputActuatorEventContext.s_EditorContext); + m_ActionSpec = CombineActuatorActionSpecs(actuators); + } +#endif + return m_ActionSpec; + } + } + + void OnDisable() + { + CleanupActionAsset(); + } + + /// + /// This method is where the gets parsed and translated into + /// s that communicate with the via a + /// virtual . + /// + /// The flow of this method is as follows: + /// + /// + /// Ensure that our custom s are registered with + /// the InputSystem. + /// + /// + /// Look for the components that are needed by this class in order to retrieve the + /// . It first looks for , if that + /// is not found, it will get the asset from the component. + /// + /// + /// Create the list s, one for each action in the default + /// as set by the component. Within the method + /// where the actuators are being created, an is also being built based + /// on the number and types of s. This will be used to create a virtual + /// with a that is specific to the + /// specified by + /// + /// + /// Create our device based on the layout that was generated and registered during + /// actuator creation. + /// + /// + /// Create an ml-agents control scheme and add it to the so + /// our virtual devices can be used. + /// + /// + /// Add our virtual to the input system. + /// + /// + /// + /// + /// A list of + public override IActuator[] CreateActuators() + { + FindNeededComponents(); + var collection = m_AssetCollection ?? m_InputAsset; + collection.Disable(); + var inputActionMap = m_InputAsset.FindActionMap(m_PlayerInput.defaultActionMap); + + RegisterLayoutBuilder(inputActionMap, m_LayoutName); + m_Device = InputSystem.AddDevice(m_LayoutName); + + var context = new InputActuatorEventContext(inputActionMap.actions.Count, m_Device); + m_Actuators = CreateActuatorsFromMap(inputActionMap, m_BehaviorParameters, m_Device, context); + + UpdateDeviceBinding(m_BehaviorParameters.IsInHeuristicMode()); + inputActionMap.Enable(); + + m_ActionSpec = CombineActuatorActionSpecs(m_Actuators); + collection.Enable(); + return m_Actuators; + } + + static ActionSpec CombineActuatorActionSpecs(IActuator[] actuators) + { + var specs = new ActionSpec[actuators.Length]; + for (var i = 0; i < actuators.Length; i++) + { + specs[i] = actuators[i].ActionSpec; + } + return ActionSpec.Combine(specs); + } + + internal static IActuator[] CreateActuatorsFromMap(InputActionMap inputActionMap, + BehaviorParameters behaviorParameters, + InputDevice inputDevice, + InputActuatorEventContext context) + { + var actuators = new IActuator[inputActionMap.actions.Count]; + for (var i = 0; i < inputActionMap.actions.Count; i++) + { + var action = inputActionMap.actions[i]; + var actionLayout = InputSystem.LoadLayout(action.expectedControlType); + var adaptor = (IRLActionInputAdaptor)Activator.CreateInstance(controlTypeToAdaptorType[actionLayout.type]); + actuators[i] = new InputActionActuator(inputDevice, behaviorParameters, action, adaptor, context); + + // Reasonably, the input system starts adding numbers after the first none numbered name + // is added. So for device ID of 0, we use the empty string in the path. + var path = $"{inputDevice?.path}{InputControlPath.Separator}{action.name}"; + action.AddBinding(path, + action.interactions, + action.processors, + mlAgentsControlSchemeName); + action.bindingMask = InputBinding.MaskByGroup(mlAgentsControlSchemeName); + } + return actuators; + } + + /// + /// Set up bindings based on whether or not the BehaviorParameters are working in Heuristic mode or not. + /// If we are working in Heuristic mode, we want the input system to handle everything. If not, we + /// want the neural network to send input from virtual devices. + /// + /// true if the Agent connected to this GameObject is working in + /// Heuristic mode. + /// + internal void UpdateDeviceBinding(bool isInHeuristicMode) + { + if (ReferenceEquals(m_Device, null)) + { + return; + } + var collection = m_AssetCollection ?? m_InputAsset; + m_ControlScheme = CreateControlScheme(m_Device, isInHeuristicMode, m_InputAsset); + if (m_InputAsset.FindControlSchemeIndex(m_ControlScheme.name) != -1) + { + m_InputAsset.RemoveControlScheme(m_ControlScheme.name); + } + + if (!isInHeuristicMode) + { + var inputActionMap = m_InputAsset.FindActionMap(m_PlayerInput.defaultActionMap); + m_InputAsset.AddControlScheme(m_ControlScheme); + collection.bindingMask = InputBinding.MaskByGroup(m_ControlScheme.bindingGroup); + collection.devices = new ReadOnlyArray(new[] { m_Device }); + inputActionMap.bindingMask = collection.bindingMask; + inputActionMap.devices = collection.devices; + } + else + { + var inputActionMap = m_InputAsset.FindActionMap(m_PlayerInput.defaultActionMap); + collection.bindingMask = null; + collection.devices = InputSystem.devices; + inputActionMap.devices = InputSystem.devices; + inputActionMap.bindingMask = null; + } + collection.Enable(); + } + + /// + /// This method creates a control scheme and adds it to the passed in so + /// we can add our device to in order for it to be discovered by the . + /// + /// The virtual device to add to our custom control scheme. + /// if we are in heuristic mode, we need to add other other device requirements. + /// The InputActionAsset to get the device requirements from + internal static InputControlScheme CreateControlScheme(InputControl device, + bool isInHeuristicMode, + InputActionAsset asset) + { + var deviceRequirements = new List + { + new InputControlScheme.DeviceRequirement + { + controlPath = InputBinding.Separator + mlAgentsLayoutName + } + }; + + if (isInHeuristicMode) + { + for (var i = 0; i < asset.controlSchemes.Count; i++) + { + var scheme = asset.controlSchemes[i]; + for (var ii = 0; ii < scheme.deviceRequirements.Count; ii++) + { + deviceRequirements.Add(scheme.deviceRequirements[ii]); + } + } + } + + var inputControlScheme = new InputControlScheme( + mlAgentsControlSchemeName, + deviceRequirements); + + return inputControlScheme; + } + + /// + /// + /// + /// + /// + internal static void RegisterLayoutBuilder(InputActionMap defaultMap, string layoutName) + { + if (InputSystem.LoadLayout(layoutName) == null) + { + InputSystem.RegisterLayoutBuilder(() => + { + // TODO does this need to change based on the action map we use? + var builder = new InputControlLayout.Builder() + .WithName(layoutName) + .WithFormat(mlAgentsLayoutFormat); + for (var i = 0; i < defaultMap.actions.Count; i++) + { + var action = defaultMap.actions[i]; + builder.AddControl(action.name) + .WithLayout(action.expectedControlType); + } + return builder.Build(); + }, layoutName); + } + } + + internal void FindNeededComponents() + { + if (m_InputAsset == null) + { + var assetProvider = GetComponent(); + Assert.IsNotNull(assetProvider); + (m_InputAsset, m_AssetCollection) = assetProvider.GetInputActionAsset(); + Assert.IsNotNull(m_InputAsset, "An InputActionAsset could not be found on IInputActionAssetProvider or PlayerInput."); + } + if (m_PlayerInput == null) + { + m_PlayerInput = GetComponent(); + Assert.IsNotNull(m_PlayerInput, "PlayerInput component could not be found on this GameObject."); + } + + if (m_BehaviorParameters == null) + { + m_BehaviorParameters = GetComponent(); + Assert.IsNotNull(m_BehaviorParameters, "BehaviorParameters were not on the current GameObject."); + m_BehaviorParameters.OnPolicyUpdated += UpdateDeviceBinding; + m_LayoutName = mlAgentsLayoutName + m_BehaviorParameters.BehaviorName; + } + } + + internal void CleanupActionAsset() + { + InputSystem.RemoveLayout(mlAgentsLayoutName); + if (!ReferenceEquals(m_Device, null)) + { + InputSystem.RemoveDevice(m_Device); + } + + if (!ReferenceEquals(m_InputAsset, null) + && m_InputAsset.FindControlSchemeIndex(mlAgentsControlSchemeName) != -1) + { + m_InputAsset.RemoveControlScheme(mlAgentsControlSchemeName); + } + + if (m_Actuators != null) + { + Array.Clear(m_Actuators, 0, m_Actuators.Length); + } + + if (!ReferenceEquals(m_BehaviorParameters, null)) + { + m_BehaviorParameters.OnPolicyUpdated -= UpdateDeviceBinding; + } + + m_InputAsset = null; + m_PlayerInput = null; + m_BehaviorParameters = null; + m_Device = null; + } + + int m_ActuatorsWrittenToEvent; + NativeArray m_InputBufferForFrame; + InputEventPtr m_InputEventPtrForFrame; + public InputEventPtr GetEventForFrame() + { +#if UNITY_EDITOR + if (!EditorApplication.isPlaying) + { + return new InputEventPtr(); + } +#endif + if (m_ActuatorsWrittenToEvent % m_Actuators.Length == 0 || !m_InputEventPtrForFrame.valid) + { + m_ActuatorsWrittenToEvent = 0; + m_InputEventPtrForFrame = new InputEventPtr(); + m_InputBufferForFrame = StateEvent.From(m_Device, out m_InputEventPtrForFrame); + } + + return m_InputEventPtrForFrame; + } + + public void EventProcessedInFrame() + { +#if UNITY_EDITOR + if (!EditorApplication.isPlaying) + { + return; + } +#endif + m_ActuatorsWrittenToEvent++; + if (m_ActuatorsWrittenToEvent == m_Actuators.Length && m_InputEventPtrForFrame.valid) + { + InputSystem.QueueEvent(m_InputEventPtrForFrame); + m_InputBufferForFrame.Dispose(); + } + } + } +} +#endif // MLA_INPUT_SYSTEM diff --git a/com.unity.ml-agents/Runtime/Input/InputActuatorComponent.cs.meta b/com.unity.ml-agents/Runtime/Input/InputActuatorComponent.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..ee3aa3d02407538ea2239492b20740c29e66e0b3 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Input/InputActuatorComponent.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Input/InputActuatorEventContext.cs b/com.unity.ml-agents/Runtime/Input/InputActuatorEventContext.cs new file mode 100644 index 0000000000000000000000000000000000000000..577d333e13ed77b242767088e2ae80dffb078c00 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Input/InputActuatorEventContext.cs @@ -0,0 +1,80 @@ +#if MLA_INPUT_SYSTEM +using System; +using Unity.Collections; +using UnityEngine.InputSystem; +using UnityEngine.InputSystem.LowLevel; +#if UNITY_EDITOR +using UnityEditor; +#endif + +namespace Unity.MLAgents.Input +{ + /// + /// This interface is passed to InputActionActuators to allow them to write to InputEvents. + /// The way this interface should be used is to request the by calling + /// then call before returning from + /// + [UnityEngine.Scripting.APIUpdating.MovedFrom("Unity.MLAgents.Extensions.Input")] + public class InputActuatorEventContext : IDisposable + { + /// + /// The number of times to allow the use of an event before queuing it in the InputSystem. + /// + public readonly int NumTimesToProcess; + public readonly InputDevice InputDevice; + NativeArray m_EventBuffer; + InputEventPtr m_Ptr; + int m_Count; + +#if UNITY_EDITOR + public static InputActuatorEventContext s_EditorContext = new InputActuatorEventContext(); +#endif + + public InputActuatorEventContext(int numTimesToProcess = 1, InputDevice device = null) + { + NumTimesToProcess = numTimesToProcess; + InputDevice = device; + m_Count = 0; + m_Ptr = new InputEventPtr(); + m_EventBuffer = new NativeArray(); + } + + /// + /// Returns the to write to for the current frame. + /// + /// The to write to for the current frame. + public IDisposable GetEventForFrame(out InputEventPtr eventPtr) + { +#if UNITY_EDITOR + if (!EditorApplication.isPlaying) + { + eventPtr = new InputEventPtr(); + } +#endif + if (m_Count % NumTimesToProcess == 0) + { + m_Count = 0; + m_EventBuffer = StateEvent.From(InputDevice, out m_Ptr); + } + eventPtr = m_Ptr; + return this; + } + + public void Dispose() + { +#if UNITY_EDITOR + if (!EditorApplication.isPlaying) + { + return; + } +#endif + m_Count++; + if (m_Count == NumTimesToProcess && m_Ptr.valid) + { + InputSystem.QueueEvent(m_Ptr); + m_EventBuffer.Dispose(); + } + } + } +} +#endif // MLA_INPUT_SYSTEM diff --git a/com.unity.ml-agents/Runtime/Input/InputActuatorEventContext.cs.meta b/com.unity.ml-agents/Runtime/Input/InputActuatorEventContext.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..bc7c18aeab9cda3c61c37d519a86b7fcbc033e15 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Input/InputActuatorEventContext.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Input/Unity.ML-Agents.Input.asmdef b/com.unity.ml-agents/Runtime/Input/Unity.ML-Agents.Input.asmdef new file mode 100644 index 0000000000000000000000000000000000000000..ee0c03550ef31b6040bc2ee3abd03f936a20137d --- /dev/null +++ b/com.unity.ml-agents/Runtime/Input/Unity.ML-Agents.Input.asmdef @@ -0,0 +1,24 @@ +{ + "name": "Unity.ML-Agents.Input", + "rootNamespace": "", + "references": [ + "Unity.ML-Agents", + "Unity.InferenceEngine", + "Unity.InputSystem" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [ + { + "name": "com.unity.inputsystem", + "expression": "1.3.0", + "define": "MLA_INPUT_SYSTEM" + } + ], + "noEngineReferences": false +} diff --git a/com.unity.ml-agents/Runtime/Input/Unity.ML-Agents.Input.asmdef.meta b/com.unity.ml-agents/Runtime/Input/Unity.ML-Agents.Input.asmdef.meta new file mode 100644 index 0000000000000000000000000000000000000000..c18dd6c365a47c7552ecd54d56cb33556105cd3f Binary files /dev/null and b/com.unity.ml-agents/Runtime/Input/Unity.ML-Agents.Input.asmdef.meta differ diff --git a/com.unity.ml-agents/Runtime/Integrations.meta b/com.unity.ml-agents/Runtime/Integrations.meta new file mode 100644 index 0000000000000000000000000000000000000000..f218be2521d7e64cf9072cc4f5d3b600b498b4cb Binary files /dev/null and b/com.unity.ml-agents/Runtime/Integrations.meta differ diff --git a/com.unity.ml-agents/Runtime/Integrations/Match3.meta b/com.unity.ml-agents/Runtime/Integrations/Match3.meta new file mode 100644 index 0000000000000000000000000000000000000000..27b09a9eff9083a935aaf3db28bdf80dae66eba9 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Integrations/Match3.meta differ diff --git a/com.unity.ml-agents/Runtime/Integrations/Match3/AbstractBoard.cs b/com.unity.ml-agents/Runtime/Integrations/Match3/AbstractBoard.cs new file mode 100644 index 0000000000000000000000000000000000000000..585be89197727e7076cc8b10469c05d8bd0ab568 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Integrations/Match3/AbstractBoard.cs @@ -0,0 +1,312 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using UnityEngine; +using Debug = UnityEngine.Debug; + +namespace Unity.MLAgents.Integrations.Match3 +{ + /// + /// Representation of the AbstractBoard dimensions, and number of cell and special types. + /// + public struct BoardSize + { + /// + /// Number of rows on the board + /// + public int Rows; + + /// + /// Number of columns on the board + /// + public int Columns; + + /// + /// Maximum number of different types of cells (colors, pieces, etc). + /// + public int NumCellTypes; + + /// + /// Maximum number of special types. This can be zero, in which case + /// all cells of the same type are assumed to be equivalent. + /// + public int NumSpecialTypes; + + /// + /// Check that all fields of the left-hand BoardSize are less than or equal to the field of the right-hand BoardSize + /// + /// The first 'BoardSize' to compare. + /// The second 'BoardSize' to compare. + /// True if all fields are less than or equal. + public static bool operator <=(BoardSize lhs, BoardSize rhs) + { + return lhs.Rows <= rhs.Rows && lhs.Columns <= rhs.Columns && lhs.NumCellTypes <= rhs.NumCellTypes && + lhs.NumSpecialTypes <= rhs.NumSpecialTypes; + } + + /// + /// Check that all fields of the left-hand BoardSize are greater than or equal to the field of the right-hand BoardSize + /// + /// The first 'BoardSize' to compare. + /// The second 'BoardSize' to compare. + /// True if all fields are greater than or equal. + public static bool operator >=(BoardSize lhs, BoardSize rhs) + { + return lhs.Rows >= rhs.Rows && lhs.Columns >= rhs.Columns && lhs.NumCellTypes >= rhs.NumCellTypes && + lhs.NumSpecialTypes >= rhs.NumSpecialTypes; + } + + /// + /// Return a string representation of the BoardSize. + /// + /// The string summary of the `BoardSize`. + public override string ToString() + { + return + $"Rows: {Rows}, Columns: {Columns}, NumCellTypes: {NumCellTypes}, NumSpecialTypes: {NumSpecialTypes}"; + } + } + + /// + /// An adapter between ML Agents and a Match-3 game. + /// + public abstract class AbstractBoard : MonoBehaviour + { + /// + /// Return the maximum size of the board. This is used to determine the size of observations and actions, + /// so the returned values must not change. + /// + /// The maxium size of the board. + public abstract BoardSize GetMaxBoardSize(); + + /// + /// Return the current size of the board. The values must less than or equal to the values returned from + /// . + /// By default, this will return ; if your board doesn't change size, you don't need to + /// override it. + /// + /// The current size of the board. + public virtual BoardSize GetCurrentBoardSize() + { + return GetMaxBoardSize(); + } + + /// + /// Returns the "color" of the piece at the given row and column. + /// This should be between 0 and BoardSize.NumCellTypes-1 (inclusive). + /// The actual order of the values doesn't matter. + /// + /// The row index. + /// The collunm index. + /// Color of piece at given row and column. + public abstract int GetCellType(int row, int col); + + /// + /// Returns the special type of the piece at the given row and column. + /// This should be between 0 and BoardSize.NumSpecialTypes (inclusive). + /// The actual order of the values doesn't matter. + /// + /// The row index. + /// The collunm index. + /// The special type of the piece at the give row and column. + public abstract int GetSpecialType(int row, int col); + + /// + /// Check whether the particular Move is valid for the game. + /// The actual results will depend on the rules of the game, but we provide + /// that handles basic match3 rules with no special or immovable pieces. + /// + /// + /// Moves that would go outside of are filtered out before they are + /// passed to IsMoveValid(). + /// + /// The move to check. + /// True if the move is valid False otherwise. + public abstract bool IsMoveValid(Move m); + + /// + /// Instruct the game to make the given . Returns true if the move was made. + /// Note that during training, a move that was marked as invalid may occasionally still be + /// requested. If this happens, it is safe to do nothing and request another move. + /// + /// The move to carry out. + /// True if the move was made, False otherwise. + public abstract bool MakeMove(Move m); + + /// + /// Return the total number of moves possible for the board. + /// + /// The total number of moves possible for the board. + public int NumMoves() + { + return Move.NumPotentialMoves(GetMaxBoardSize()); + } + + /// + /// An optional callback for when the all moves are invalid. Ideally, the game state should + /// be changed before this happens, but this is a way to get notified if not. + /// + public Action OnNoValidMovesAction; + + /// + /// Iterate through all moves on the board. + /// + /// The `IEnumerator` for iterating all moves. + public IEnumerable AllMoves() + { + var maxBoardSize = GetMaxBoardSize(); + var currentBoardSize = GetCurrentBoardSize(); + + var currentMove = Move.FromMoveIndex(0, maxBoardSize); + for (var i = 0; i < NumMoves(); i++) + { + if (currentMove.InRangeForBoard(currentBoardSize)) + { + yield return currentMove; + } + currentMove.Next(maxBoardSize); + } + } + + /// + /// Iterate through all valid moves on the board. + /// + /// The `IEnumerator` for iterating the valid moves. + public IEnumerable ValidMoves() + { + var maxBoardSize = GetMaxBoardSize(); + var currentBoardSize = GetCurrentBoardSize(); + + var currentMove = Move.FromMoveIndex(0, maxBoardSize); + for (var i = 0; i < NumMoves(); i++) + { + if (currentMove.InRangeForBoard(currentBoardSize) && IsMoveValid(currentMove)) + { + yield return currentMove; + } + currentMove.Next(maxBoardSize); + } + } + + /// + /// Returns true if swapping the cells specified by the move would result in + /// 3 or more cells of the same type in a row. This assumes that all pieces are allowed + /// to be moved; to add extra logic, incorporate it into your method. + /// + /// The `Move`. + /// True if swapping the cells would result in 3 or more cells of the same type in a row. + public bool SimpleIsMoveValid(Move move) + { + using (TimerStack.Instance.Scoped("SimpleIsMoveValid")) + { + var moveVal = GetCellType(move.Row, move.Column); + var (otherRow, otherCol) = move.OtherCell(); + var oppositeVal = GetCellType(otherRow, otherCol); + + // Simple check - if the values are the same, don't match + // This might not be valid for all games + { + if (moveVal == oppositeVal) + { + return false; + } + } + + bool moveMatches = CheckHalfMove(otherRow, otherCol, moveVal, move.Direction); + if (moveMatches) + { + // early out + return true; + } + + bool otherMatches = CheckHalfMove(move.Row, move.Column, oppositeVal, move.OtherDirection()); + return otherMatches; + } + } + + /// + /// Check if one of the cells that is swapped during a move matches 3 or more. + /// Since these checks are similar for each cell, we consider the move as two "half moves". + /// + /// + /// + /// + /// + /// True if one of the cells that is swapped during a move matches 3 or more, False otherwise. + bool CheckHalfMove(int newRow, int newCol, int newValue, Direction incomingDirection) + { + var currentBoardSize = GetCurrentBoardSize(); + int matchedLeft = 0, matchedRight = 0, matchedUp = 0, matchedDown = 0; + + if (incomingDirection != Direction.Right) + { + for (var c = newCol - 1; c >= 0; c--) + { + if (GetCellType(newRow, c) == newValue) + matchedLeft++; + else + break; + } + } + + if (incomingDirection != Direction.Left) + { + for (var c = newCol + 1; c < currentBoardSize.Columns; c++) + { + if (GetCellType(newRow, c) == newValue) + matchedRight++; + else + break; + } + } + + if (incomingDirection != Direction.Down) + { + for (var r = newRow + 1; r < currentBoardSize.Rows; r++) + { + if (GetCellType(r, newCol) == newValue) + matchedUp++; + else + break; + } + } + + if (incomingDirection != Direction.Up) + { + for (var r = newRow - 1; r >= 0; r--) + { + if (GetCellType(r, newCol) == newValue) + matchedDown++; + else + break; + } + } + + if ((matchedUp + matchedDown >= 2) || (matchedLeft + matchedRight >= 2)) + { + return true; + } + + return false; + } + + /// + /// Make sure that the current BoardSize isn't larger than the original value of . + /// If it is, log a warning. + /// + /// + [Conditional("DEBUG")] + internal void CheckBoardSizes(BoardSize originalMaxBoardSize) + { + var currentBoardSize = GetCurrentBoardSize(); + if (!(currentBoardSize <= originalMaxBoardSize)) + { + Debug.LogWarning( + "Current BoardSize is larger than maximum board size was on initialization. This may cause unexpected results.\n" + + $"Original GetMaxBoardSize() result: {originalMaxBoardSize}\n" + + $"GetCurrentBoardSize() result: {currentBoardSize}" + ); + } + } + } +} diff --git a/com.unity.ml-agents/Runtime/Integrations/Match3/AbstractBoard.cs.meta b/com.unity.ml-agents/Runtime/Integrations/Match3/AbstractBoard.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..42019368a138e8d64c5a8017c161fc47fa980b8b Binary files /dev/null and b/com.unity.ml-agents/Runtime/Integrations/Match3/AbstractBoard.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Integrations/Match3/Match3Actuator.cs b/com.unity.ml-agents/Runtime/Integrations/Match3/Match3Actuator.cs new file mode 100644 index 0000000000000000000000000000000000000000..3c919079bd8b46136b315eed441882cfcf06697a --- /dev/null +++ b/com.unity.ml-agents/Runtime/Integrations/Match3/Match3Actuator.cs @@ -0,0 +1,188 @@ +using Unity.MLAgents.Actuators; +using Debug = UnityEngine.Debug; + + +namespace Unity.MLAgents.Integrations.Match3 +{ + /// + /// Actuator for a Match3 game. It translates valid moves (defined by AbstractBoard.IsMoveValid()) + /// in action masks, and applies the action to the board via AbstractBoard.MakeMove(). + /// + public class Match3Actuator : IActuator, IBuiltInActuator + { + AbstractBoard m_Board; + System.Random m_Random; + ActionSpec m_ActionSpec; + bool m_ForceHeuristic; + BoardSize m_MaxBoardSize; + + /// + /// Create a Match3Actuator. + /// + /// Board + /// Whether the inference action should be ignored and the Agent's Heuristic + /// should be called. This should only be used for generating comparison stats of the Heuristic. + /// The seed used to initialize . + /// Name + public Match3Actuator(AbstractBoard board, + bool forceHeuristic, + int seed, + string name) + { + m_Board = board; + m_MaxBoardSize = m_Board.GetMaxBoardSize(); + Name = name; + + m_ForceHeuristic = forceHeuristic; + + var numMoves = Move.NumPotentialMoves(m_MaxBoardSize); + m_ActionSpec = ActionSpec.MakeDiscrete(numMoves); + m_Random = new System.Random(seed); + } + + /// + public ActionSpec ActionSpec => m_ActionSpec; + + /// + public void OnActionReceived(ActionBuffers actions) + { + m_Board.CheckBoardSizes(m_MaxBoardSize); + if (m_ForceHeuristic) + { + Heuristic(actions); + } + var moveIndex = actions.DiscreteActions[0]; + + Move move = Move.FromMoveIndex(moveIndex, m_MaxBoardSize); + m_Board.MakeMove(move); + } + + /// + public void WriteDiscreteActionMask(IDiscreteActionMask actionMask) + { + var currentBoardSize = m_Board.GetCurrentBoardSize(); + m_Board.CheckBoardSizes(m_MaxBoardSize); + const int branch = 0; + bool foundValidMove = false; + using (TimerStack.Instance.Scoped("WriteDiscreteActionMask")) + { + var numMoves = m_Board.NumMoves(); + + var currentMove = Move.FromMoveIndex(0, m_MaxBoardSize); + for (var i = 0; i < numMoves; i++) + { + // Check that the move is allowed for the current boardSize (e.g. it won't move a piece out of + // bounds), and that it's allowed by the game itself. + if (currentMove.InRangeForBoard(currentBoardSize) && m_Board.IsMoveValid(currentMove)) + { + foundValidMove = true; + } + else + { + actionMask.SetActionEnabled(branch, i, false); + } + currentMove.Next(m_MaxBoardSize); + } + + if (!foundValidMove) + { + // If all the moves are invalid and we mask all the actions out, this will cause an assert + // later on in IDiscreteActionMask. Instead, fire a callback to the user if they provided one, + // (or log a warning if not) and leave the last action unmasked. This isn't great, but + // an invalid move should be easier to handle than an exception.. + if (m_Board.OnNoValidMovesAction != null) + { + m_Board.OnNoValidMovesAction(); + } + else + { + Debug.LogWarning( + "No valid moves are available. The last action will be left unmasked, so " + + "an invalid move will be passed to AbstractBoard.MakeMove()." + ); + } + actionMask.SetActionEnabled(branch, numMoves - 1, true); + } + } + } + + /// + public string Name { get; } + + /// + public void ResetData() + { + } + + /// + public BuiltInActuatorType GetBuiltInActuatorType() + { + return BuiltInActuatorType.Match3Actuator; + } + + /// + public void Heuristic(in ActionBuffers actionsOut) + { + var discreteActions = actionsOut.DiscreteActions; + discreteActions[0] = GreedyMove(); + } + + /// + /// Returns a valid move that gives the highest value for EvalMovePoints(). If multiple moves have the same + /// value, one of them will be chosen with uniform probability. + /// + /// + /// By default, EvalMovePoints() returns 1, so all valid moves are equally likely. Inherit from this class and + /// override EvalMovePoints() to use your game's scoring as a better estimate. + /// + /// Valid mode. + internal int GreedyMove() + { + var bestMoveIndex = 0; + var bestMovePoints = -1; + var numMovesAtCurrentScore = 0; + + foreach (var move in m_Board.ValidMoves()) + { + var movePoints = EvalMovePoints(move); + if (movePoints < bestMovePoints) + { + // Worse, skip + continue; + } + + if (movePoints > bestMovePoints) + { + // Better, keep + bestMovePoints = movePoints; + bestMoveIndex = move.MoveIndex; + numMovesAtCurrentScore = 1; + } + else + { + // Tied for best - use reservoir sampling to make sure we select from equal moves uniformly. + // See https://en.wikipedia.org/wiki/Reservoir_sampling#Simple_algorithm + numMovesAtCurrentScore++; + var randVal = m_Random.Next(0, numMovesAtCurrentScore); + if (randVal == 0) + { + // Keep the new one + bestMoveIndex = move.MoveIndex; + } + } + } + + return bestMoveIndex; + } + + /// + /// Method to be overridden when evaluating how many points a specific move will generate. + /// + /// The move to evaluate. + /// The number of points the move generates. + protected virtual int EvalMovePoints(Move move) + { + return 1; + } + } +} diff --git a/com.unity.ml-agents/Runtime/Integrations/Match3/Match3Actuator.cs.meta b/com.unity.ml-agents/Runtime/Integrations/Match3/Match3Actuator.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..4052f5e51fa4da65a4cd0b21d36953e74d0b1113 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Integrations/Match3/Match3Actuator.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Integrations/Match3/Match3ActuatorComponent.cs b/com.unity.ml-agents/Runtime/Integrations/Match3/Match3ActuatorComponent.cs new file mode 100644 index 0000000000000000000000000000000000000000..39470dc6981fee0ccdaa39cef4db0c38bc7330b1 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Integrations/Match3/Match3ActuatorComponent.cs @@ -0,0 +1,90 @@ +using System; +using Unity.MLAgents.Actuators; +using UnityEngine; +using UnityEngine.Serialization; + +namespace Unity.MLAgents.Integrations.Match3 +{ + /// + /// Actuator component for a Match3 game. Generates a Match3Actuator at runtime. + /// + [AddComponentMenu("ML Agents/Match 3 Actuator", (int)MenuGroup.Actuators)] + public class Match3ActuatorComponent : ActuatorComponent + { + [HideInInspector, SerializeField, FormerlySerializedAs("ActuatorName")] + string m_ActuatorName = "Match3 Actuator"; + + /// + /// Name of the generated Match3Actuator object. + /// Note that changing this at runtime does not affect how the Agent sorts the actuators. + /// + public string ActuatorName + { + get => m_ActuatorName; + set => m_ActuatorName = value; + } + + [HideInInspector, SerializeField, FormerlySerializedAs("RandomSeed")] + int m_RandomSeed = -1; + + /// + /// A random seed used in the actuator's heuristic, if needed. + /// + public int RandomSeed + { + get => m_RandomSeed; + set => m_RandomSeed = value; + } + + [HideInInspector, SerializeField, FormerlySerializedAs("ForceHeuristic")] + [Tooltip("Force using the Agent's Heuristic() method to decide the action. This should only be used in testing.")] + bool m_ForceHeuristic; + + /// + /// Force using the Agent's Heuristic() method to decide the action. This should only be used in testing. + /// + public bool ForceHeuristic + { + get => m_ForceHeuristic; + set => m_ForceHeuristic = value; + } + + int CreateNewSeed() + { +#if UNITY_6000_3_OR_NEWER + return gameObject.GetEntityId().GetHashCode(); +#else + return gameObject.GetInstanceID(); +#endif + } + + /// + public override IActuator[] CreateActuators() + { + var board = GetComponent(); + if (!board) + { + return Array.Empty(); + } + + var seed = m_RandomSeed == -1 ? CreateNewSeed() : m_RandomSeed + 1; + return new IActuator[] { new Match3Actuator(board, m_ForceHeuristic, seed, m_ActuatorName) }; + } + + /// + public override ActionSpec ActionSpec + { + get + { + var board = GetComponent(); + if (board == null) + { + return ActionSpec.MakeContinuous(0); + } + + var numMoves = Move.NumPotentialMoves(board.GetMaxBoardSize()); + return ActionSpec.MakeDiscrete(numMoves); + } + } + } +} diff --git a/com.unity.ml-agents/Runtime/Integrations/Match3/Match3ActuatorComponent.cs.meta b/com.unity.ml-agents/Runtime/Integrations/Match3/Match3ActuatorComponent.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..c592f8be7a4ac6f587251d6c4627152ab26b2f01 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Integrations/Match3/Match3ActuatorComponent.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Integrations/Match3/Match3Sensor.cs b/com.unity.ml-agents/Runtime/Integrations/Match3/Match3Sensor.cs new file mode 100644 index 0000000000000000000000000000000000000000..0e99a44c8c1013bc27c09b0fad03a7e5af53e4eb --- /dev/null +++ b/com.unity.ml-agents/Runtime/Integrations/Match3/Match3Sensor.cs @@ -0,0 +1,351 @@ +using System; +using System.Collections.Generic; +using Unity.MLAgents.Sensors; +using UnityEngine; + +namespace Unity.MLAgents.Integrations.Match3 +{ + /// + /// Delegate that provides integer values at a given (x,y) coordinate. + /// + /// X + /// Y + /// The integer value at the given (x,y) coordinate. + public delegate int GridValueProvider(int x, int y); + + /// + /// Type of observations to generate. + /// + /// + public enum Match3ObservationType + { + /// + /// Generate a one-hot encoding of the cell type for each cell on the board. If there are special types, + /// these will also be one-hot encoded. + /// + Vector, + + /// + /// Generate a one-hot encoding of the cell type for each cell on the board, but arranged as + /// a Rows x Columns visual observation. If there are special types, these will also be one-hot encoded. + /// + UncompressedVisual, + + /// + /// Generate a one-hot encoding of the cell type for each cell on the board, but arranged as + /// a Rows x Columns visual observation. If there are special types, these will also be one-hot encoded. + /// During training, these will be sent as a concatenated series of PNG images, with 3 channels per image. + /// + CompressedVisual + } + + /// + /// Sensor for Match3 games. Can generate either vector, compressed visual, + /// or uncompressed visual observations. Uses a GridValueProvider to determine the observation values. + /// + public class Match3Sensor : ISensor, IBuiltInSensor, IDisposable + { + Match3ObservationType m_ObservationType; + ObservationSpec m_ObservationSpec; + string m_Name; + + AbstractBoard m_Board; + BoardSize m_MaxBoardSize; + GridValueProvider m_GridValues; + int m_OneHotSize; + + Texture2D m_ObservationTexture; + OneHotToTextureUtil m_TextureUtil; + + /// + /// Create a sensor for the GridValueProvider with the specified observation type. + /// + /// + /// Use Match3Sensor.CellTypeSensor() or Match3Sensor.SpecialTypeSensor() instead of calling + /// the constructor directly. + /// + /// The abstract board. + /// The GridValueProvider, should be either board.GetCellType or board.GetSpecialType. + /// The number of possible values that the GridValueProvider can return. + /// Whether to produce vector or visual observations + /// Name of the sensor. + public Match3Sensor(AbstractBoard board, GridValueProvider gvp, int oneHotSize, Match3ObservationType obsType, string name) + { + var maxBoardSize = board.GetMaxBoardSize(); + m_Name = name; + m_MaxBoardSize = maxBoardSize; + m_GridValues = gvp; + m_OneHotSize = oneHotSize; + m_Board = board; + + m_ObservationType = obsType; + m_ObservationSpec = obsType == Match3ObservationType.Vector + ? ObservationSpec.Vector(maxBoardSize.Rows * maxBoardSize.Columns * oneHotSize) + : ObservationSpec.Visual(oneHotSize, maxBoardSize.Rows, maxBoardSize.Columns); + } + + /// + /// Create a sensor that encodes the board cells as observations. + /// + /// The abstract board. + /// Whether to produce vector or visual observations + /// Name of the sensor. + /// `Match3Sensor` that encodes the board cells as observations. + public static Match3Sensor CellTypeSensor(AbstractBoard board, Match3ObservationType obsType, string name) + { + var maxBoardSize = board.GetMaxBoardSize(); + return new Match3Sensor(board, board.GetCellType, maxBoardSize.NumCellTypes, obsType, name); + } + + /// + /// Create a sensor that encodes the cell special types as observations. Returns null if the board's + /// NumSpecialTypes is 0 (indicating the sensor isn't needed). + /// + /// The abstract board. + /// Whether to produce vector or visual observations + /// Name of the sensor. + /// `Match3Sensor` that encodes the board cell special types as observations. + public static Match3Sensor SpecialTypeSensor(AbstractBoard board, Match3ObservationType obsType, string name) + { + var maxBoardSize = board.GetMaxBoardSize(); + if (maxBoardSize.NumSpecialTypes == 0) + { + return null; + } + var specialSize = maxBoardSize.NumSpecialTypes + 1; + return new Match3Sensor(board, board.GetSpecialType, specialSize, obsType, name); + } + + /// + public ObservationSpec GetObservationSpec() + { + return m_ObservationSpec; + } + + /// + public int Write(ObservationWriter writer) + { + m_Board.CheckBoardSizes(m_MaxBoardSize); + var currentBoardSize = m_Board.GetCurrentBoardSize(); + + int offset = 0; + var isVisual = m_ObservationType != Match3ObservationType.Vector; + + // This is equivalent to + // for (var r = 0; r < m_MaxBoardSize.Rows; r++) + // for (var c = 0; c < m_MaxBoardSize.Columns; c++) + // if (r < currentBoardSize.Rows && c < currentBoardSize.Columns) + // WriteOneHot + // else + // WriteZero + // but rearranged to avoid the branching. + + for (var r = 0; r < currentBoardSize.Rows; r++) + { + for (var c = 0; c < currentBoardSize.Columns; c++) + { + var val = m_GridValues(r, c); + writer.WriteOneHot(offset, r, c, val, m_OneHotSize, isVisual); + offset += m_OneHotSize; + } + + for (var c = currentBoardSize.Columns; c < m_MaxBoardSize.Columns; c++) + { + writer.WriteZero(offset, r, c, m_OneHotSize, isVisual); + offset += m_OneHotSize; + } + } + + for (var r = currentBoardSize.Rows; r < m_MaxBoardSize.Columns; r++) + { + for (var c = 0; c < m_MaxBoardSize.Columns; c++) + { + writer.WriteZero(offset, r, c, m_OneHotSize, isVisual); + offset += m_OneHotSize; + } + } + + return offset; + } + + /// + public byte[] GetCompressedObservation() + { + m_Board.CheckBoardSizes(m_MaxBoardSize); + var height = m_MaxBoardSize.Rows; + var width = m_MaxBoardSize.Columns; + if (ReferenceEquals(null, m_ObservationTexture)) + { + m_ObservationTexture = new Texture2D(width, height, TextureFormat.RGB24, false); + } + + if (ReferenceEquals(null, m_TextureUtil)) + { + m_TextureUtil = new OneHotToTextureUtil(height, width); + } + var bytesOut = new List(); + var currentBoardSize = m_Board.GetCurrentBoardSize(); + + // Encode the cell types or special types as batches of PNGs + // This is potentially wasteful, e.g. if there are 4 cell types and 1 special type, we could + // fit in in 2 images, but we'll use 3 total (2 PNGs for the 4 cell type channels, and 1 for + // the special types). + var numCellImages = (m_OneHotSize + 2) / 3; + for (var i = 0; i < numCellImages; i++) + { + m_TextureUtil.EncodeToTexture( + m_GridValues, + m_ObservationTexture, + 3 * i, + currentBoardSize.Rows, + currentBoardSize.Columns + ); + bytesOut.AddRange(m_ObservationTexture.EncodeToPNG()); + } + + return bytesOut.ToArray(); + } + + /// + public void Update() + { + } + + /// + public void Reset() + { + } + + internal SensorCompressionType GetCompressionType() + { + return m_ObservationType == Match3ObservationType.CompressedVisual ? + SensorCompressionType.PNG : + SensorCompressionType.None; + } + + /// + public CompressionSpec GetCompressionSpec() + { + return new CompressionSpec(GetCompressionType()); + } + + /// + public string GetName() + { + return m_Name; + } + + /// + public BuiltInSensorType GetBuiltInSensorType() + { + return BuiltInSensorType.Match3Sensor; + } + + /// + /// Clean up the owned Texture2D. + /// + public void Dispose() + { + if (!ReferenceEquals(null, m_ObservationTexture)) + { + Utilities.DestroyTexture(m_ObservationTexture); + m_ObservationTexture = null; + } + } + } + + /// + /// Utility class for converting a 2D array of ints representing a one-hot encoding into + /// a texture, suitable for conversion to PNGs for observations. + /// Works by encoding 3 values at a time as pixels in the texture, thus it should be + /// called (maxValue + 2) / 3 times, increasing the channelOffset by 3 each time. + /// + internal class OneHotToTextureUtil + { + Color[] m_Colors; + int m_MaxHeight; + int m_MaxWidth; + private static readonly Color[] s_OneHotColors = { Color.red, Color.green, Color.blue }; + + public OneHotToTextureUtil(int maxHeight, int maxWidth) + { + m_Colors = new Color[maxHeight * maxWidth]; + m_MaxHeight = maxHeight; + m_MaxWidth = maxWidth; + } + + public void EncodeToTexture( + GridValueProvider gridValueProvider, + Texture2D texture, + int channelOffset, + int currentHeight, + int currentWidth + ) + { + var i = 0; + // There's an implicit flip converting to PNG from texture, so make sure we + // counteract that when forming the texture by iterating through h in reverse. + for (var h = m_MaxHeight - 1; h >= 0; h--) + { + for (var w = 0; w < m_MaxWidth; w++) + { + var colorVal = Color.black; + if (h < currentHeight && w < currentWidth) + { + int oneHotValue = gridValueProvider(h, w); + if (oneHotValue >= channelOffset && oneHotValue < channelOffset + 3) + { + colorVal = s_OneHotColors[oneHotValue - channelOffset]; + } + } + m_Colors[i++] = colorVal; + } + } + texture.SetPixels(m_Colors); + } + } + + /// + /// Utility methods for writing one-hot observations. + /// + internal static class ObservationWriterMatch3Extensions + { + public static void WriteOneHot(this ObservationWriter writer, int offset, int row, int col, int value, int oneHotSize, bool isVisual) + { + if (isVisual) + { + for (var i = 0; i < oneHotSize; i++) + { + writer[i, row, col] = (i == value) ? 1.0f : 0.0f; + } + } + else + { + for (var i = 0; i < oneHotSize; i++) + { + writer[offset] = (i == value) ? 1.0f : 0.0f; + offset++; + } + } + } + + public static void WriteZero(this ObservationWriter writer, int offset, int row, int col, int oneHotSize, bool isVisual) + { + if (isVisual) + { + for (var i = 0; i < oneHotSize; i++) + { + writer[i, row, col] = 0.0f; + } + } + else + { + for (var i = 0; i < oneHotSize; i++) + { + writer[offset] = 0.0f; + offset++; + } + } + } + } +} diff --git a/com.unity.ml-agents/Runtime/Integrations/Match3/Match3Sensor.cs.meta b/com.unity.ml-agents/Runtime/Integrations/Match3/Match3Sensor.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..b440cac0fc7bd9e81f502d6a3becc8bdcde5db20 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Integrations/Match3/Match3Sensor.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Integrations/Match3/Match3SensorComponent.cs b/com.unity.ml-agents/Runtime/Integrations/Match3/Match3SensorComponent.cs new file mode 100644 index 0000000000000000000000000000000000000000..8afd4d0edcc83964ed7261eb5bee67b374e567d3 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Integrations/Match3/Match3SensorComponent.cs @@ -0,0 +1,77 @@ +using System; +using Unity.MLAgents.Sensors; +using UnityEngine; +using UnityEngine.Serialization; + +namespace Unity.MLAgents.Integrations.Match3 +{ + /// + /// Sensor component for a Match3 game. + /// + [AddComponentMenu("ML Agents/Match 3 Sensor", (int)MenuGroup.Sensors)] + public class Match3SensorComponent : SensorComponent, IDisposable + { + [HideInInspector, SerializeField, FormerlySerializedAs("SensorName")] + string m_SensorName = "Match3 Sensor"; + + /// + /// Name of the generated Match3Sensor object. + /// Note that changing this at runtime does not affect how the Agent sorts the sensors. + /// + public string SensorName + { + get => m_SensorName; + set => m_SensorName = value; + } + + [HideInInspector, SerializeField, FormerlySerializedAs("ObservationType")] + Match3ObservationType m_ObservationType = Match3ObservationType.Vector; + + /// + /// Type of observation to generate. + /// + public Match3ObservationType ObservationType + { + get => m_ObservationType; + set => m_ObservationType = value; + } + + private ISensor[] m_Sensors; + + /// + public override ISensor[] CreateSensors() + { + // Clean up any existing sensors + Dispose(); + + var board = GetComponent(); + if (!board) + { + return Array.Empty(); + } + var cellSensor = Match3Sensor.CellTypeSensor(board, m_ObservationType, m_SensorName + " (cells)"); + // This can be null if BoardSize.NumSpecialTypes is 0 + var specialSensor = Match3Sensor.SpecialTypeSensor(board, m_ObservationType, m_SensorName + " (special)"); + m_Sensors = specialSensor != null + ? new ISensor[] { cellSensor, specialSensor } + : new ISensor[] { cellSensor }; + return m_Sensors; + } + + /// + /// Clean up the sensors created by CreateSensors(). + /// + public void Dispose() + { + if (m_Sensors != null) + { + for (var i = 0; i < m_Sensors.Length; i++) + { + ((Match3Sensor)m_Sensors[i]).Dispose(); + } + + m_Sensors = null; + } + } + } +} diff --git a/com.unity.ml-agents/Runtime/Integrations/Match3/Match3SensorComponent.cs.meta b/com.unity.ml-agents/Runtime/Integrations/Match3/Match3SensorComponent.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..d2d2713eefad4b5dbe30173a33186389cafd45c4 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Integrations/Match3/Match3SensorComponent.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Integrations/Match3/Move.cs b/com.unity.ml-agents/Runtime/Integrations/Match3/Move.cs new file mode 100644 index 0000000000000000000000000000000000000000..8fc1af6bb9ae5cc3782b46cfac9e4fdd0bf4614d --- /dev/null +++ b/com.unity.ml-agents/Runtime/Integrations/Match3/Move.cs @@ -0,0 +1,277 @@ +using System; +using UnityEngine; + +namespace Unity.MLAgents.Integrations.Match3 +{ + /// + /// Directions for a Move. + /// + public enum Direction + { + /// + /// Move up (increasing row direction). + /// + Up, + + /// + /// Move down (decreasing row direction). + /// + Down, // -row direction + + /// + /// Move left (decreasing column direction). + /// + Left, // -column direction + + /// + /// Move right (increasing column direction). + /// + Right, // +column direction + } + + /// + /// Struct that encapsulates a swap of adjacent cells. + /// A Move can be constructed from either a starting row, column, and direction, + /// or from a "move index" between 0 and NumPotentialMoves()-1. + /// Moves are enumerated as the internal edges of the game grid. + /// Left/right moves come first. There are (maxCols - 1) * maxRows of these. + /// Up/down moves are next. There are (maxRows - 1) * maxCols of these. + /// + public struct Move + { + /// + /// Index of the move, from 0 to NumPotentialMoves-1. + /// + public int MoveIndex; + + /// + /// Row of the cell that will be moved. + /// + public int Row; + + /// + /// Column of the cell that will be moved. + /// + public int Column; + + /// + /// Direction that the cell will be moved. + /// + public Direction Direction; + + /// + /// Construct a Move from its move index and the board size. + /// This is useful for iterating through all the Moves on a board, or constructing + /// the Move corresponding to an Agent decision. + /// + /// Must be between 0 and NumPotentialMoves(maxRows, maxCols). + /// Max board size + /// Corresponding `Move`. + /// Argument out of range + public static Move FromMoveIndex(int moveIndex, BoardSize maxBoardSize) + { + var maxRows = maxBoardSize.Rows; + var maxCols = maxBoardSize.Columns; + + if (moveIndex < 0 || moveIndex >= NumPotentialMoves(maxBoardSize)) + { + throw new ArgumentOutOfRangeException("moveIndex"); + } + Direction dir; + int row, col; + if (moveIndex < (maxCols - 1) * maxRows) + { + dir = Direction.Right; + col = moveIndex % (maxCols - 1); + row = moveIndex / (maxCols - 1); + } + else + { + dir = Direction.Up; + var offset = moveIndex - (maxCols - 1) * maxRows; + col = offset % maxCols; + row = offset / maxCols; + } + return new Move + { + MoveIndex = moveIndex, + Direction = dir, + Row = row, + Column = col + }; + } + + /// + /// Increment the Move to the next MoveIndex, and update the Row, Column, and Direction accordingly. + /// + /// Max board size + public void Next(BoardSize maxBoardSize) + { + var maxRows = maxBoardSize.Rows; + var maxCols = maxBoardSize.Columns; + + var switchoverIndex = (maxCols - 1) * maxRows; + + MoveIndex++; + if (MoveIndex < switchoverIndex) + { + Column++; + if (Column == maxCols - 1) + { + Row++; + Column = 0; + } + } + else if (MoveIndex == switchoverIndex) + { + // switch from moving right to moving up + Row = 0; + Column = 0; + Direction = Direction.Up; + } + else + { + Column++; + if (Column == maxCols) + { + Row++; + Column = 0; + } + } + } + + /// + /// Construct a Move from the row, column, direction, and board size. + /// + /// Row + /// Col + /// Dir + /// Max board size + /// Corresponding `Move`. + public static Move FromPositionAndDirection(int row, int col, Direction dir, BoardSize maxBoardSize) + { + // Check for out-of-bounds + if (row < 0 || row >= maxBoardSize.Rows) + { + throw new IndexOutOfRangeException($"row was {row}, but must be between 0 and {maxBoardSize.Rows - 1}."); + } + + if (col < 0 || col >= maxBoardSize.Columns) + { + throw new IndexOutOfRangeException($"col was {col}, but must be between 0 and {maxBoardSize.Columns - 1}."); + } + + // Check moves that would go out of bounds e.g. col == 0 and dir == Left + if ( + row == 0 && dir == Direction.Down || + row == maxBoardSize.Rows - 1 && dir == Direction.Up || + col == 0 && dir == Direction.Left || + col == maxBoardSize.Columns - 1 && dir == Direction.Right + ) + { + throw new IndexOutOfRangeException($"Cannot move cell at row={row} col={col} in Direction={dir}"); + } + + // Normalize - only consider Right and Up + if (dir == Direction.Left) + { + dir = Direction.Right; + col = col - 1; + } + else if (dir == Direction.Down) + { + dir = Direction.Up; + row = row - 1; + } + + int moveIndex; + if (dir == Direction.Right) + { + moveIndex = col + row * (maxBoardSize.Columns - 1); + } + else + { + var offset = (maxBoardSize.Columns - 1) * maxBoardSize.Rows; + moveIndex = offset + col + row * maxBoardSize.Columns; + } + + return new Move + { + Row = row, + Column = col, + Direction = dir, + MoveIndex = moveIndex, + }; + } + + /// + /// Check if the move is valid for the given board size. + /// This will be passed the return value from AbstractBoard.GetCurrentBoardSize(). + /// + /// Board size + /// True if move is valide given input `boardSize`, False if not. + public bool InRangeForBoard(BoardSize boardSize) + { + var (otherRow, otherCol) = OtherCell(); + // Get the maximum row and column this move would affect. + var maxMoveRow = Mathf.Max(Row, otherRow); + var maxMoveCol = Mathf.Max(Column, otherCol); + return maxMoveRow < boardSize.Rows && maxMoveCol < boardSize.Columns; + } + + /// + /// Get the other row and column that correspond to this move. + /// + /// Corresponding other (row, column) tuple for this move. + /// Argument out of range + public (int Row, int Column) OtherCell() + { + switch (Direction) + { + case Direction.Up: + return (Row + 1, Column); + case Direction.Down: + return (Row - 1, Column); + case Direction.Left: + return (Row, Column - 1); + case Direction.Right: + return (Row, Column + 1); + default: + throw new ArgumentOutOfRangeException(); + } + } + + /// + /// Get the opposite direction of this move. + /// + /// Oposit `Direction` of this move. + /// Argument out of range + public Direction OtherDirection() + { + switch (Direction) + { + case Direction.Up: + return Direction.Down; + case Direction.Down: + return Direction.Up; + case Direction.Left: + return Direction.Right; + case Direction.Right: + return Direction.Left; + default: + throw new ArgumentOutOfRangeException(); + } + } + + /// + /// Return the number of potential moves for a board of the given size. + /// This is equivalent to the number of internal edges in the board. + /// + /// Max board size + /// Number of potential moves given a boardsize. + public static int NumPotentialMoves(BoardSize maxBoardSize) + { + return maxBoardSize.Rows * (maxBoardSize.Columns - 1) + (maxBoardSize.Rows - 1) * (maxBoardSize.Columns); + } + } +} diff --git a/com.unity.ml-agents/Runtime/Integrations/Match3/Move.cs.meta b/com.unity.ml-agents/Runtime/Integrations/Match3/Move.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..1457c24b13647dcb16a00be1cfc10caf769df8ce Binary files /dev/null and b/com.unity.ml-agents/Runtime/Integrations/Match3/Move.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/MLAgentsSettings.cs b/com.unity.ml-agents/Runtime/MLAgentsSettings.cs new file mode 100644 index 0000000000000000000000000000000000000000..a86cb3635cbed03413de73a835d23b972eeac8d9 --- /dev/null +++ b/com.unity.ml-agents/Runtime/MLAgentsSettings.cs @@ -0,0 +1,41 @@ +using UnityEngine; +using System.Runtime.CompilerServices; + + +[assembly: InternalsVisibleTo("Unity.ML-Agents.DevTests.Editor")] +namespace Unity.MLAgents +{ + internal class MLAgentsSettings : ScriptableObject + { + [SerializeField] + private bool m_ConnectTrainer = true; + [SerializeField] + private int m_EditorPort = 5004; + + public bool ConnectTrainer + { + get { return m_ConnectTrainer; } + set + { + m_ConnectTrainer = value; + OnChange(); + } + } + + public int EditorPort + { + get { return m_EditorPort; } + set + { + m_EditorPort = value; + OnChange(); + } + } + + internal void OnChange() + { + if (MLAgentsSettingsManager.Settings == this) + MLAgentsSettingsManager.ApplySettings(); + } + } +} diff --git a/com.unity.ml-agents/Runtime/MLAgentsSettings.cs.meta b/com.unity.ml-agents/Runtime/MLAgentsSettings.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..90c1507a50764545784db79c7b58d5d331430af0 Binary files /dev/null and b/com.unity.ml-agents/Runtime/MLAgentsSettings.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/MLAgentsSettingsManager.cs b/com.unity.ml-agents/Runtime/MLAgentsSettingsManager.cs new file mode 100644 index 0000000000000000000000000000000000000000..b68bfc43798e16d21e2a71fc663f6d924bb2e386 --- /dev/null +++ b/com.unity.ml-agents/Runtime/MLAgentsSettingsManager.cs @@ -0,0 +1,101 @@ +using System; +using UnityEngine; +#if UNITY_EDITOR +using UnityEditor; +#else +using System.Linq; +#endif + +namespace Unity.MLAgents +{ +#if UNITY_EDITOR + [InitializeOnLoad] +#endif + internal static class MLAgentsSettingsManager + { + internal static event Action OnSettingsChange; + internal const string EditorBuildSettingsConfigKey = "com.unity.ml-agents.settings"; + private static MLAgentsSettings s_Settings; +#if UNITY_EDITOR + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] + static void ResetStaticsOnLoad() + { + s_Settings = null; + OnSettingsChange = null; + } +#endif + + // setter will trigger callback for refreshing editor UI if using editor + public static MLAgentsSettings Settings + { + get + { + if (s_Settings == null) + { + Initialize(); + } + return s_Settings; + } + set + { + Debug.Assert(value != null); +#if UNITY_EDITOR + if (!string.IsNullOrEmpty(AssetDatabase.GetAssetPath(value))) + { + EditorBuildSettings.AddConfigObject(EditorBuildSettingsConfigKey, value, true); + } +#endif + s_Settings = value; + ApplySettings(); + } + } + + static MLAgentsSettingsManager() + { + Initialize(); + } + + static void Initialize() + { +#if UNITY_EDITOR + InitializeInEditor(); +#else + InitializeInPlayer(); +#endif + } + +#if UNITY_EDITOR + internal static void InitializeInEditor() + { + var settings = ScriptableObject.CreateInstance(); + if (EditorBuildSettings.TryGetConfigObject(EditorBuildSettingsConfigKey, + out MLAgentsSettings settingsAsset)) + { + if (settingsAsset != null) + { + settings = settingsAsset; + } + } + Settings = settings; + } + +#else + internal static void InitializeInPlayer() + { + Settings = Resources.FindObjectsOfTypeAll().FirstOrDefault() ?? ScriptableObject.CreateInstance(); + } + +#endif + + internal static void ApplySettings() + { + OnSettingsChange?.Invoke(); + } + + internal static void Destroy() + { + s_Settings = null; + OnSettingsChange = null; + } + } +} diff --git a/com.unity.ml-agents/Runtime/MLAgentsSettingsManager.cs.meta b/com.unity.ml-agents/Runtime/MLAgentsSettingsManager.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..9a6f0c3a12fa659618406d8081a8d9ed317dd3d2 Binary files /dev/null and b/com.unity.ml-agents/Runtime/MLAgentsSettingsManager.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/MultiAgentGroupIdCounter.cs b/com.unity.ml-agents/Runtime/MultiAgentGroupIdCounter.cs new file mode 100644 index 0000000000000000000000000000000000000000..47aa61299e040f2c4e606ce859abdce920f78f94 --- /dev/null +++ b/com.unity.ml-agents/Runtime/MultiAgentGroupIdCounter.cs @@ -0,0 +1,13 @@ +using System.Threading; + +namespace Unity.MLAgents +{ + internal static class MultiAgentGroupIdCounter + { + static int s_Counter; + public static int GetGroupId() + { + return Interlocked.Increment(ref s_Counter); + } + } +} diff --git a/com.unity.ml-agents/Runtime/MultiAgentGroupIdCounter.cs.meta b/com.unity.ml-agents/Runtime/MultiAgentGroupIdCounter.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..b4298cdc957d4448065604bbedc2330033b5b560 Binary files /dev/null and b/com.unity.ml-agents/Runtime/MultiAgentGroupIdCounter.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Policies.meta b/com.unity.ml-agents/Runtime/Policies.meta new file mode 100644 index 0000000000000000000000000000000000000000..6353357f7bce8fbda1e88e420d6f26835ba0d27d Binary files /dev/null and b/com.unity.ml-agents/Runtime/Policies.meta differ diff --git a/com.unity.ml-agents/Runtime/Policies/BehaviorParameters.cs b/com.unity.ml-agents/Runtime/Policies/BehaviorParameters.cs new file mode 100644 index 0000000000000000000000000000000000000000..5fd8d9aad7aa7bd37881ac140b3c0556b77ff621 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Policies/BehaviorParameters.cs @@ -0,0 +1,294 @@ +using Unity.InferenceEngine; +using System; +using UnityEngine; +using UnityEngine.Serialization; +using Unity.MLAgents.Actuators; +using Unity.MLAgents.Sensors.Reflection; + +namespace Unity.MLAgents.Policies +{ + /// + /// Defines what type of behavior the Agent will be using + /// + [Serializable] + public enum BehaviorType + { + /// + /// The Agent will use the remote process for decision making. + /// if unavailable, will use inference and if no model is provided, will use + /// the heuristic. + /// + Default, + + /// + /// The Agent will always use its heuristic + /// + HeuristicOnly, + + /// + /// The Agent will always use inference with the provided + /// neural network model. + /// + InferenceOnly + } + + /// + /// Options for controlling how the Agent class is searched for s. + /// + public enum ObservableAttributeOptions + { + /// + /// All ObservableAttributes on the Agent will be ignored. This is the + /// default behavior. If there are no ObservableAttributes on the + /// Agent, this will result in the fastest initialization time. + /// + Ignore, + + /// + /// Only members on the declared class will be examined; members that are + /// inherited are ignored. This is a reasonable tradeoff between + /// performance and flexibility. + /// + /// This corresponds to setting the + /// [BindingFlags.DeclaredOnly](https://docs.microsoft.com/en-us/dotnet/api/system.reflection.bindingflags?view=netcore-3.1) + /// when examining the fields and properties of the Agent class instance. + /// + ExcludeInherited, + + /// + /// All members on the class will be examined. This can lead to slower + /// startup times. + /// + ExamineAll + } + + /// + /// A component for setting an instance's behavior and + /// brain properties. + /// + /// At runtime, this component generates the agent's policy objects + /// according to the settings you specified in the Editor. + [AddComponentMenu("ML Agents/Behavior Parameters", (int)MenuGroup.Default)] + public class BehaviorParameters : MonoBehaviour + { + [HideInInspector, SerializeField] + BrainParameters m_BrainParameters = new BrainParameters(); + + /// + /// Delegate for receiving events about Policy Updates. + /// + /// Whether or not the current policy is running in heuristic mode. + public delegate void PolicyUpdated(bool isInHeuristicMode); + + /// + /// Event that fires when an Agent's policy is updated. + /// + internal event PolicyUpdated OnPolicyUpdated; + + /// + /// The associated for this behavior. + /// + public BrainParameters BrainParameters + { + get { return m_BrainParameters; } + internal set { m_BrainParameters = value; } + } + + [HideInInspector, SerializeField] + ModelAsset m_Model; + + /// + /// The neural network model used when in inference mode. + /// This should not be set at runtime; use + /// to set it instead. + /// + public ModelAsset Model + { + get { return m_Model; } + set { m_Model = value; UpdateAgentPolicy(); } + } + + [HideInInspector, SerializeField] + InferenceDevice m_InferenceDevice = InferenceDevice.Default; + + /// + /// How inference is performed for this Agent's model. + /// This should not be set at runtime; use + /// to set it instead. + /// + public InferenceDevice InferenceDevice + { + get { return m_InferenceDevice; } + set { m_InferenceDevice = value; UpdateAgentPolicy(); } + } + + [HideInInspector, SerializeField] + BehaviorType m_BehaviorType; + + /// + /// The BehaviorType for the Agent. + /// + public BehaviorType BehaviorType + { + get { return m_BehaviorType; } + set { m_BehaviorType = value; UpdateAgentPolicy(); } + } + + [HideInInspector, SerializeField] + string m_BehaviorName = "My Behavior"; + + /// + /// The name of this behavior, which is used as a base name. See + /// for the full name. + /// This should not be set at runtime; use + /// to set it instead. + /// + public string BehaviorName + { + get { return m_BehaviorName; } + set { m_BehaviorName = value; UpdateAgentPolicy(); } + } + + /// + /// The team ID for this behavior. + /// + [HideInInspector, SerializeField, FormerlySerializedAs("m_TeamID")] + public int TeamId; + // TODO properties here instead of Agent + + [FormerlySerializedAs("m_useChildSensors")] + [HideInInspector] + [SerializeField] + [Tooltip("Use all Sensor components attached to child GameObjects of this Agent.")] + bool m_UseChildSensors = true; + + [HideInInspector] + [SerializeField] + [Tooltip("Use all Actuator components attached to child GameObjects of this Agent.")] + bool m_UseChildActuators = true; + + /// + /// Whether or not to use all the sensor components attached to child GameObjects of the agent. + /// Note that changing this after the Agent has been initialized will not have any effect. + /// + public bool UseChildSensors + { + get { return m_UseChildSensors; } + set { m_UseChildSensors = value; } + } + + [HideInInspector] + [SerializeField] + [Tooltip("Set action selection to deterministic, Only applies to inference from within unity.")] + private bool m_DeterministicInference = false; + + /// + /// Whether to select actions deterministically during inference from the provided neural network. + /// + public bool DeterministicInference + { + get { return m_DeterministicInference; } + set { m_DeterministicInference = value; } + } + + /// + /// Whether or not to use all the actuator components attached to child GameObjects of the agent. + /// Note that changing this after the Agent has been initialized will not have any effect. + /// + public bool UseChildActuators + { + get { return m_UseChildActuators; } + set { m_UseChildActuators = value; } + } + + [HideInInspector, SerializeField] + ObservableAttributeOptions m_ObservableAttributeHandling = ObservableAttributeOptions.Ignore; + + /// + /// Determines how the Agent class is searched for s. + /// + public ObservableAttributeOptions ObservableAttributeHandling + { + get { return m_ObservableAttributeHandling; } + set { m_ObservableAttributeHandling = value; } + } + + /// + /// Returns the behavior name, concatenated with any other metadata (i.e. team id). + /// + public string FullyQualifiedBehaviorName + { + get { return m_BehaviorName + "?team=" + TeamId; } + } + + void Awake() + { + OnPolicyUpdated += mode => { }; + } + + internal IPolicy GeneratePolicy(ActionSpec actionSpec, ActuatorManager actuatorManager) + { + switch (m_BehaviorType) + { + case BehaviorType.HeuristicOnly: + return new HeuristicPolicy(actuatorManager, actionSpec); + case BehaviorType.InferenceOnly: + { + if (m_Model == null) + { + var behaviorType = BehaviorType.InferenceOnly.ToString(); + throw new UnityAgentsException( + $"Can't use Behavior Type {behaviorType} without a model. " + + "Either assign a model, or change to a different Behavior Type." + ); + } + return new SentisPolicy(actionSpec, actuatorManager, m_Model, m_InferenceDevice, m_BehaviorName, m_DeterministicInference); + } + case BehaviorType.Default: + if (Academy.Instance.IsCommunicatorOn) + { + return new RemotePolicy(actionSpec, actuatorManager, FullyQualifiedBehaviorName); + } + if (m_Model != null) + { + return new SentisPolicy(actionSpec, actuatorManager, m_Model, m_InferenceDevice, m_BehaviorName, m_DeterministicInference); + } + else + { + return new HeuristicPolicy(actuatorManager, actionSpec); + } + default: + return new HeuristicPolicy(actuatorManager, actionSpec); + } + } + + /// + /// Query the behavior parameters in order to see if the Agent is running in Heuristic Mode. + /// + /// true if the Agent is running in Heuristic mode. + public bool IsInHeuristicMode() + { + if (BehaviorType == BehaviorType.HeuristicOnly) + { + return true; + } + + return BehaviorType == BehaviorType.Default && + ReferenceEquals(Model, null) && + (!Academy.IsInitialized || + Academy.IsInitialized && + !Academy.Instance.IsCommunicatorOn); + } + + internal void UpdateAgentPolicy() + { + var agent = GetComponent(); + if (agent == null) + { + return; + } + agent.ReloadPolicy(); + OnPolicyUpdated?.Invoke(IsInHeuristicMode()); + } + } +} diff --git a/com.unity.ml-agents/Runtime/Policies/BehaviorParameters.cs.meta b/com.unity.ml-agents/Runtime/Policies/BehaviorParameters.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..507c417e97363973a49783181ec92a2a9de28f41 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Policies/BehaviorParameters.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Policies/BrainParameters.cs b/com.unity.ml-agents/Runtime/Policies/BrainParameters.cs new file mode 100644 index 0000000000000000000000000000000000000000..59b1f0e8c09e6e1a051598d8024b1347e392d3e1 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Policies/BrainParameters.cs @@ -0,0 +1,192 @@ +using System; +using UnityEngine; +using UnityEngine.Serialization; +using Unity.MLAgents.Actuators; + +namespace Unity.MLAgents.Policies +{ + /// + /// This is deprecated. Agents can now use both continuous and discrete actions together. + /// + [Obsolete("Continuous and discrete actions on the same Agent are now supported; see ActionSpec.")] + internal enum SpaceType + { + /// + /// Discrete action space: a fixed number of options are available. + /// + Discrete, + + /// + /// Continuous action space: each action can take on a float value. + /// + Continuous + } + + /// + /// Holds information about the brain. It defines what are the inputs and outputs of the + /// decision process. + /// + /// + /// Set brain parameters for an instance using the + /// component attached to the agent's [GameObject]. + /// + /// [GameObject]: https://docs.unity3d.com/Manual/GameObjects.html + /// + [Serializable] + public class BrainParameters : ISerializationCallbackReceiver + { + /// + /// The number of the observations that are added in + /// + /// + /// + /// The length of the vector containing observation values. + /// + [FormerlySerializedAs("vectorObservationSize")] + public int VectorObservationSize = 1; + + /// + /// Stacking refers to concatenating the observations across multiple frames. This field + /// indicates the number of frames to concatenate across. + /// + [FormerlySerializedAs("numStackedVectorObservations")] + [Range(1, 50)] public int NumStackedVectorObservations = 1; + + [SerializeField] + internal ActionSpec m_ActionSpec = new ActionSpec(0, null); + + /// + /// The specification of the Actions for the BrainParameters. + /// + public ActionSpec ActionSpec + { + get { return m_ActionSpec; } + set + { + m_ActionSpec.NumContinuousActions = value.NumContinuousActions; + m_ActionSpec.BranchSizes = value.BranchSizes; + SyncDeprecatedActionFields(); + } + } + + /// + /// (Deprecated) The number of possible actions. + /// + /// The size specified is interpreted differently depending on whether + /// the agent uses the continuous or the discrete actions. + /// + /// For the continuous actions: the length of the float vector that represents + /// the action. + /// For the discrete actions: the number of branches. + /// + [Obsolete("VectorActionSize has been deprecated, please use ActionSpec instead.")] + [SerializeField] + [FormerlySerializedAs("vectorActionSize")] + internal int[] VectorActionSize = new[] { 1 }; + + /// + /// The list of strings describing what the actions correspond to. + /// + [FormerlySerializedAs("vectorActionDescriptions")] + public string[] VectorActionDescriptions; + + /// + /// (Deprecated) Defines if the action is discrete or continuous. + /// + [Obsolete("VectorActionSpaceType has been deprecated, please use ActionSpec instead.")] + [SerializeField] + [FormerlySerializedAs("vectorActionSpaceType")] + internal SpaceType VectorActionSpaceType = SpaceType.Discrete; + + [SerializeField] + [HideInInspector] + internal bool hasUpgradedBrainParametersWithActionSpec; + + /// + /// Deep clones the BrainParameter object. + /// + /// A new BrainParameter object with the same values as the original. + public BrainParameters Clone() + { + // Disable deprecation warnings so we can read/write the old fields. +#pragma warning disable CS0618 + return new BrainParameters + { + VectorObservationSize = VectorObservationSize, + NumStackedVectorObservations = NumStackedVectorObservations, + VectorActionDescriptions = (string[])VectorActionDescriptions.Clone(), + ActionSpec = new ActionSpec(ActionSpec.NumContinuousActions, ActionSpec.BranchSizes), + VectorActionSize = (int[])VectorActionSize.Clone(), + VectorActionSpaceType = VectorActionSpaceType, + }; +#pragma warning restore CS0618 + } + + /// + /// Propagate ActionSpec fields from deprecated fields + /// + private void UpdateToActionSpec() + { + // Disable deprecation warnings so we can read the old fields. +#pragma warning disable CS0618 + if (!hasUpgradedBrainParametersWithActionSpec + && m_ActionSpec.NumContinuousActions == 0 + && m_ActionSpec.NumDiscreteActions == 0) + { + if (VectorActionSpaceType == SpaceType.Continuous) + { + m_ActionSpec.NumContinuousActions = VectorActionSize[0]; + } + if (VectorActionSpaceType == SpaceType.Discrete) + { + m_ActionSpec.BranchSizes = (int[])VectorActionSize.Clone(); + } + } + hasUpgradedBrainParametersWithActionSpec = true; +#pragma warning restore CS0618 + } + + /// + /// Sync values in ActionSpec fields to deprecated fields + /// + private void SyncDeprecatedActionFields() + { + // Disable deprecation warnings so we can read the old fields. +#pragma warning disable CS0618 + + if (m_ActionSpec.NumContinuousActions == 0) + { + VectorActionSize = (int[])ActionSpec.BranchSizes.Clone(); + VectorActionSpaceType = SpaceType.Discrete; + } + else if (m_ActionSpec.NumDiscreteActions == 0) + { + VectorActionSize = new[] { m_ActionSpec.NumContinuousActions }; + VectorActionSpaceType = SpaceType.Continuous; + } + else + { + VectorActionSize = null; + } +#pragma warning restore CS0618 + } + + /// + /// Called by Unity immediately before serializing this object. + /// + public void OnBeforeSerialize() + { + UpdateToActionSpec(); + SyncDeprecatedActionFields(); + } + + /// + /// Called by Unity immediately after deserializing this object. + /// + public void OnAfterDeserialize() + { + UpdateToActionSpec(); + SyncDeprecatedActionFields(); + } + } +} diff --git a/com.unity.ml-agents/Runtime/Policies/BrainParameters.cs.meta b/com.unity.ml-agents/Runtime/Policies/BrainParameters.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..248b4d0f6d6441f55117ce054e1ca0c9d3cb655e Binary files /dev/null and b/com.unity.ml-agents/Runtime/Policies/BrainParameters.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Policies/HeuristicPolicy.cs b/com.unity.ml-agents/Runtime/Policies/HeuristicPolicy.cs new file mode 100644 index 0000000000000000000000000000000000000000..8e5333874a68c4646c7875fd6d7efe891da7994a --- /dev/null +++ b/com.unity.ml-agents/Runtime/Policies/HeuristicPolicy.cs @@ -0,0 +1,142 @@ +using System.Collections.Generic; +using System; +using System.Collections; +using Unity.MLAgents.Actuators; +using Unity.MLAgents.Sensors; + +namespace Unity.MLAgents.Policies +{ + /// + /// The Heuristic Policy uses a hard-coded Heuristic method + /// to take decisions each time the RequestDecision method is + /// called. + /// + internal class HeuristicPolicy : IPolicy + { + ActuatorManager m_ActuatorManager; + ActionBuffers m_ActionBuffers; + bool m_Done; + bool m_DecisionRequested; + + ObservationWriter m_ObservationWriter = new ObservationWriter(); + NullList m_NullList = new NullList(); + + + public HeuristicPolicy(ActuatorManager actuatorManager, ActionSpec actionSpec) + { + m_ActuatorManager = actuatorManager; + var numContinuousActions = actionSpec.NumContinuousActions; + var numDiscreteActions = actionSpec.NumDiscreteActions; + var continuousDecision = new ActionSegment(new float[numContinuousActions], 0, numContinuousActions); + var discreteDecision = new ActionSegment(new int[numDiscreteActions], 0, numDiscreteActions); + m_ActionBuffers = new ActionBuffers(continuousDecision, discreteDecision); + } + + /// + public void RequestDecision(AgentInfo info, List sensors) + { + StepSensors(sensors); + m_Done = info.done; + m_DecisionRequested = true; + } + + /// + public ref readonly ActionBuffers DecideAction() + { + if (!m_Done && m_DecisionRequested) + { + m_ActionBuffers.Clear(); + m_ActuatorManager.ApplyHeuristic(m_ActionBuffers); + } + m_DecisionRequested = false; + return ref m_ActionBuffers; + } + + public void Dispose() + { + } + + /// + /// Trivial implementation of the IList interface that does nothing. + /// This is only used for "writing" observations that we will discard. + /// + internal class NullList : IList + { + public IEnumerator GetEnumerator() + { + throw new NotImplementedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public void Add(float item) + { + } + + public void Clear() + { + } + + public bool Contains(float item) + { + return false; + } + + public void CopyTo(float[] array, int arrayIndex) + { + throw new NotImplementedException(); + } + + public bool Remove(float item) + { + return false; + } + + public int Count { get; } + public bool IsReadOnly { get; } + public int IndexOf(float item) + { + return -1; + } + + public void Insert(int index, float item) + { + } + + public void RemoveAt(int index) + { + } + + public float this[int index] + { + get { return 0.0f; } + set { } + } + } + + /// + /// Run ISensor.Write or ISensor.GetCompressedObservation for each sensor + /// The output is currently unused, but this makes the sensor usage consistent + /// between training and inference. + /// + /// + void StepSensors(List sensors) + { + foreach (var sensor in sensors) + { + if (sensor.GetCompressionSpec().SensorCompressionType == SensorCompressionType.None) + { + m_ObservationWriter.SetTarget(m_NullList, sensor.GetObservationSpec(), 0); + sensor.Write(m_ObservationWriter); + } + else + { + sensor.GetCompressedObservation(); + } + } + } + } +} diff --git a/com.unity.ml-agents/Runtime/Policies/HeuristicPolicy.cs.meta b/com.unity.ml-agents/Runtime/Policies/HeuristicPolicy.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..ae074f5727d993fb7ee184a4a5d8b9e1ea0b6b6d Binary files /dev/null and b/com.unity.ml-agents/Runtime/Policies/HeuristicPolicy.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Policies/IPolicy.cs b/com.unity.ml-agents/Runtime/Policies/IPolicy.cs new file mode 100644 index 0000000000000000000000000000000000000000..4079a1f25ae418ee6b08e7801c2009c2161c58ee --- /dev/null +++ b/com.unity.ml-agents/Runtime/Policies/IPolicy.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using Unity.MLAgents.Actuators; +using Unity.MLAgents.Sensors; + +namespace Unity.MLAgents.Policies +{ + /// + /// IPolicy is connected to a single Agent. Each time the agent needs + /// a decision, it will request a decision to the Policy. The decision + /// will not be taken immediately but will be taken before or when + /// DecideAction is called. + /// + internal interface IPolicy : IDisposable + { + /// + /// Signals the Brain that the Agent needs a Decision. The Policy + /// will make the decision at a later time to allow possible + /// batching of requests. + /// + /// + /// + void RequestDecision(AgentInfo info, List sensors); + + /// + /// Signals the Policy that if the Decision has not been taken yet, + /// it must be taken now. The Brain is expected to update the actions + /// of the Agents at this point the latest. + /// + ref readonly ActionBuffers DecideAction(); + } +} diff --git a/com.unity.ml-agents/Runtime/Policies/IPolicy.cs.meta b/com.unity.ml-agents/Runtime/Policies/IPolicy.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..f43c4ddc8bb13b945853588983c2a5810ca875a2 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Policies/IPolicy.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Policies/RemotePolicy.cs b/com.unity.ml-agents/Runtime/Policies/RemotePolicy.cs new file mode 100644 index 0000000000000000000000000000000000000000..faa8a37e60ae843fe0b41ebf467ad0b655ec6340 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Policies/RemotePolicy.cs @@ -0,0 +1,77 @@ +using System.Collections.Generic; +using System.Diagnostics; +using Unity.MLAgents.Actuators; +using Unity.MLAgents.Sensors; +using Unity.MLAgents.Analytics; + + +namespace Unity.MLAgents.Policies +{ + /// + /// The Remote Policy only works when training. + /// When training your Agents, the RemotePolicy will be controlled by Python. + /// + internal class RemotePolicy : IPolicy + { + int m_AgentId; + string m_FullyQualifiedBehaviorName; + ActionSpec m_ActionSpec; + ActionBuffers m_LastActionBuffer; + bool m_AnalyticsSent; + + internal ICommunicator m_Communicator; + + /// + /// List of actuators, only used for analytics + /// + private IList m_Actuators; + + public RemotePolicy( + ActionSpec actionSpec, + IList actuators, + string fullyQualifiedBehaviorName) + { + m_FullyQualifiedBehaviorName = fullyQualifiedBehaviorName; + m_Communicator = Academy.Instance.Communicator; + m_Communicator?.SubscribeBrain(m_FullyQualifiedBehaviorName, actionSpec); + m_ActionSpec = actionSpec; + m_Actuators = actuators; + } + + /// + public void RequestDecision(AgentInfo info, List sensors) + { + SendAnalytics(sensors); + m_AgentId = info.episodeId; + m_Communicator?.PutObservations(m_FullyQualifiedBehaviorName, info, sensors); + } + + [Conditional("MLA_UNITY_ANALYTICS_MODULE")] + void SendAnalytics(IList sensors) + { + if (!m_AnalyticsSent) + { + m_AnalyticsSent = true; + TrainingAnalytics.RemotePolicyInitialized( + m_FullyQualifiedBehaviorName, + sensors, + m_ActionSpec, + m_Actuators + ); + } + } + + /// + public ref readonly ActionBuffers DecideAction() + { + m_Communicator?.DecideBatch(); + var actions = m_Communicator?.GetActions(m_FullyQualifiedBehaviorName, m_AgentId); + m_LastActionBuffer = actions == null ? ActionBuffers.Empty : (ActionBuffers)actions; + return ref m_LastActionBuffer; + } + + public void Dispose() + { + } + } +} diff --git a/com.unity.ml-agents/Runtime/Policies/RemotePolicy.cs.meta b/com.unity.ml-agents/Runtime/Policies/RemotePolicy.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..08996fa8a24457935858ab3b1a7b5aae474a4bd0 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Policies/RemotePolicy.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Policies/SentisPolicy.cs b/com.unity.ml-agents/Runtime/Policies/SentisPolicy.cs new file mode 100644 index 0000000000000000000000000000000000000000..796f907b9b1b2b1e418fce8da3a4b75030bc7675 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Policies/SentisPolicy.cs @@ -0,0 +1,144 @@ +using Unity.InferenceEngine; +using System.Collections.Generic; +using System.Diagnostics; +using Unity.MLAgents.Actuators; +using Unity.MLAgents.Inference; +using Unity.MLAgents.Sensors; + +namespace Unity.MLAgents.Policies +{ + /// + /// Where to perform inference. + /// + public enum InferenceDevice + { + /// + /// Default inference. This is currently the same as Burst, but may change in the future. + /// + Default = 0, + + /// + /// GPU inference with the Compute Shader backend. Corresponds to WorkerFactory.Type.ComputeShader in Sentis. + /// + ComputeShader = 1, + + /// + /// CPU inference using Burst. Corresponds to WorkerFactory.Type.CSharpBurst in Sentis. + /// + Burst = 2, + + /// + /// GPU inference with the Pixel Shader backend. Corresponds to in WorkerFactory.Type.PixelShader Sentis. + /// Burst is recommended instead; this is kept for legacy compatibility. + /// + PixelShader = 3, + } + + /// + /// The Sentis Policy uses a Sentis Model to make decisions at + /// every step. It uses a ModelRunner that is shared across all + /// Sentis Policies that use the same model and inference devices. + /// + internal class SentisPolicy : IPolicy + { + protected ModelRunner m_ModelRunner; + ActionBuffers m_LastActionBuffer; + + int m_AgentId; + /// + /// Inference only: set to true if the action selection from model should be + /// deterministic. + /// + bool m_DeterministicInference; + + /// + /// Sensor shapes for the associated Agents. All Agents must have the same shapes for their Sensors. + /// + List m_SensorShapes; + ActionSpec m_ActionSpec; + + private string m_BehaviorName; + + /// + /// List of actuators, only used for analytics + /// + private IList m_Actuators; + + /// + /// Whether or not we've tried to send analytics for this model. We only ever try to send once per policy, + /// and do additional deduplication in the analytics code. + /// + private bool m_AnalyticsSent; + + /// + /// Instantiate a SentisPolicy with the necessary objects for it to run. + /// + /// The action spec of the behavior. + /// The actuators used for this behavior. + /// The Neural Network to use. + /// Which device Sentis will run on. + /// The name of the behavior. + /// Inference only: set to true if the action selection from model should be + /// deterministic. + public SentisPolicy( + ActionSpec actionSpec, + IList actuators, + ModelAsset model, + InferenceDevice inferenceDevice, + string behaviorName, + bool deterministicInference = false + ) + { + var modelRunner = Academy.Instance.GetOrCreateModelRunner(model, actionSpec, inferenceDevice, deterministicInference); + m_ModelRunner = modelRunner; + m_BehaviorName = behaviorName; + m_ActionSpec = actionSpec; + m_Actuators = actuators; + m_DeterministicInference = deterministicInference; + } + + /// + public void RequestDecision(AgentInfo info, List sensors) + { + SendAnalytics(sensors); + m_AgentId = info.episodeId; + m_ModelRunner?.PutObservations(info, sensors); + } + + [Conditional("MLA_UNITY_ANALYTICS_MODULE")] + void SendAnalytics(IList sensors) + { + if (!m_AnalyticsSent) + { + m_AnalyticsSent = true; + Analytics.InferenceAnalytics.InferenceModelSet( + m_ModelRunner.Model, + m_BehaviorName, + m_ModelRunner.InferenceDevice, + sensors, + m_ActionSpec, + m_Actuators + ); + } + } + + /// + public ref readonly ActionBuffers DecideAction() + { + if (m_ModelRunner == null) + { + m_LastActionBuffer = ActionBuffers.Empty; + } + else + { + m_ModelRunner?.DecideBatch(); + m_LastActionBuffer = m_ModelRunner.GetAction(m_AgentId); + } + return ref m_LastActionBuffer; + } + + public void Dispose() + { + } + } +} diff --git a/com.unity.ml-agents/Runtime/Policies/SentisPolicy.cs.meta b/com.unity.ml-agents/Runtime/Policies/SentisPolicy.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..014a05302df5ba5118dd65ff307fe2d3a891c4a3 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Policies/SentisPolicy.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/RecursionChecker.cs b/com.unity.ml-agents/Runtime/RecursionChecker.cs new file mode 100644 index 0000000000000000000000000000000000000000..ac411fa837149370201dede2a9362da7b191a0d2 --- /dev/null +++ b/com.unity.ml-agents/Runtime/RecursionChecker.cs @@ -0,0 +1,35 @@ +using System; + +namespace Unity.MLAgents +{ + internal class RecursionChecker : IDisposable + { + private bool m_IsRunning; + private string m_MethodName; + + public RecursionChecker(string methodName) + { + m_MethodName = methodName; + } + + public IDisposable Start() + { + if (m_IsRunning) + { + throw new UnityAgentsException( + $"{m_MethodName} called recursively. " + + "This might happen if you call EnvironmentStep() or EndEpisode() from custom " + + "code such as CollectObservations() or OnActionReceived()." + ); + } + m_IsRunning = true; + return this; + } + + public void Dispose() + { + // Reset the flag when we're done (or if an exception occurred). + m_IsRunning = false; + } + } +} diff --git a/com.unity.ml-agents/Runtime/RecursionChecker.cs.meta b/com.unity.ml-agents/Runtime/RecursionChecker.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..4b2363f80906b8ccf68a5dfcd06d80e7f43d2ce7 Binary files /dev/null and b/com.unity.ml-agents/Runtime/RecursionChecker.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Sampler.cs b/com.unity.ml-agents/Runtime/Sampler.cs new file mode 100644 index 0000000000000000000000000000000000000000..15a83937d1a1b78ffb6b41402b094185d7e5a7ff --- /dev/null +++ b/com.unity.ml-agents/Runtime/Sampler.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using Unity.MLAgents.Inference.Utils; +using Random = System.Random; + +namespace Unity.MLAgents +{ + /// + /// Takes a list of floats that encode a sampling distribution and returns the sampling function. + /// + internal static class SamplerFactory + { + public static Func CreateUniformSampler(float min, float max, int seed) + { + Random distr = new Random(seed); + return () => min + (float)distr.NextDouble() * (max - min); + } + + public static Func CreateGaussianSampler(float mean, float stddev, int seed) + { + RandomNormal distr = new RandomNormal(seed, mean, stddev); + return () => (float)distr.NextDouble(); + } + + public static Func CreateMultiRangeUniformSampler(IList intervals, int seed) + { + //RNG + Random distr = new Random(seed); + // Will be used to normalize intervalFuncs + float sumIntervalSizes = 0; + //The number of intervals + int numIntervals = (intervals.Count / 2); + // List that will store interval lengths + float[] intervalSizes = new float[numIntervals]; + // List that will store uniform distributions + IList> intervalFuncs = new Func[numIntervals]; + // Collect all intervals and store as uniform distrus + // Collect all interval sizes + for (int i = 0; i < numIntervals; i++) + { + var min = intervals[2 * i]; + var max = intervals[2 * i + 1]; + var intervalSize = max - min; + sumIntervalSizes += intervalSize; + intervalSizes[i] = intervalSize; + intervalFuncs[i] = () => min + (float)distr.NextDouble() * intervalSize; + } + // Normalize interval lengths + for (int i = 0; i < numIntervals; i++) + { + intervalSizes[i] = intervalSizes[i] / sumIntervalSizes; + } + // Build cmf for intervals + for (int i = 1; i < numIntervals; i++) + { + intervalSizes[i] += intervalSizes[i - 1]; + } + Multinomial intervalDistr = new Multinomial(seed + 1); + float MultiRange() + { + int sampledInterval = intervalDistr.Sample(intervalSizes); + return intervalFuncs[sampledInterval].Invoke(); + } + + return MultiRange; + } + } +} diff --git a/com.unity.ml-agents/Runtime/Sampler.cs.meta b/com.unity.ml-agents/Runtime/Sampler.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..950e28c5b679a43193a313640b699ff9a46e446f Binary files /dev/null and b/com.unity.ml-agents/Runtime/Sampler.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/SensorHelper.cs b/com.unity.ml-agents/Runtime/SensorHelper.cs new file mode 100644 index 0000000000000000000000000000000000000000..e4dc9f227d17cb73ca3ed728bb714b76f92b3094 --- /dev/null +++ b/com.unity.ml-agents/Runtime/SensorHelper.cs @@ -0,0 +1,136 @@ +using Unity.InferenceEngine; +using Unity.MLAgents.Inference; + +namespace Unity.MLAgents.Sensors +{ + /// + /// Utility methods related to implementations. + /// + public static class SensorHelper + { + /// + /// Generates the observations for the provided sensor, and returns true if they equal the + /// expected values. If they are unequal, errorMessage is also set. + /// This should not generally be used in production code. It is only intended for + /// simplifying unit tests. + /// + /// The `ISensor` to compare observation from. + /// The expected observations. + /// The error message to throw if sensor observation doesn't match. + /// True if the observations for the provided sensor equal the expected values, False if not. + public static bool CompareObservation(ISensor sensor, float[] expected, out string errorMessage) + { + var numExpected = expected.Length; + const float fill = -1337f; + var output = new float[numExpected]; + for (var i = 0; i < numExpected; i++) + { + output[i] = fill; + } + + if (numExpected > 0) + { + if (fill != output[0]) + { + errorMessage = "Error setting output buffer."; + return false; + } + } + + ObservationWriter writer = new ObservationWriter(); + writer.SetTarget(output, sensor.GetObservationSpec(), 0); + + // Make sure ObservationWriter didn't touch anything + if (numExpected > 0) + { + if (fill != output[0]) + { + errorMessage = "ObservationWriter.SetTarget modified a buffer it shouldn't have."; + return false; + } + } + + sensor.Write(writer); + bool mismatch = false; + errorMessage = null; + for (var i = 0; i < output.Length; i++) + { + if (expected[i] != output[i]) + { + string error = $"Expected and actual differed in position {i}. Expected: {expected[i]} Actual: {output[i]} "; + errorMessage = !mismatch ? error : $"{errorMessage}\n{error}"; + mismatch = true; + } + } + if (mismatch) + { + return false; + } + + return true; + } + + /// + /// Generates the observations for the provided sensor, and returns true if they equal the + /// expected values. If they are unequal, errorMessage is also set. + /// This should not generally be used in production code. It is only intended for + /// simplifying unit tests. + /// + /// `ISensor` to generate observation from. + /// The expected observations. + /// The error message to throw if sensor observation doesn't match. + /// True if the generated observation for the provided sensor equal the expected values, False if not. + public static bool CompareObservation(ISensor sensor, float[,,] expected, out string errorMessage) + { + var tensorShape = new TensorShape(0, expected.GetLength(0), expected.GetLength(1), expected.GetLength(2)); + var numExpected = tensorShape.Height() * tensorShape.Width() * tensorShape.Channels(); + const float fill = -1337f; + var output = new float[numExpected]; + for (var i = 0; i < numExpected; i++) + { + output[i] = fill; + } + + if (numExpected > 0) + { + if (fill != output[0]) + { + errorMessage = "Error setting output buffer."; + return false; + } + } + + ObservationWriter writer = new ObservationWriter(); + writer.SetTarget(output, sensor.GetObservationSpec(), 0); + + // Make sure ObservationWriter didn't touch anything + if (numExpected > 0) + { + if (fill != output[0]) + { + errorMessage = "ObservationWriter.SetTarget modified a buffer it shouldn't have."; + return false; + } + } + + sensor.Write(writer); + for (var h = 0; h < tensorShape.Height(); h++) + { + for (var w = 0; w < tensorShape.Width(); w++) + { + for (var c = 0; c < tensorShape.Channels(); c++) + { + if (expected[c, h, w] != output[tensorShape.Index(0, c, h, w)]) + { + errorMessage = $"Expected and actual differed in position [{c}, {h}, {w}]. " + + $"Expected: {expected[c, h, w]} Actual: {output[tensorShape.Index(0, c, h, w)]} "; + return false; + } + } + } + } + errorMessage = null; + return true; + } + } +} diff --git a/com.unity.ml-agents/Runtime/SensorHelper.cs.meta b/com.unity.ml-agents/Runtime/SensorHelper.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..c331abd0b6a4be0050e5fe40f8cdc0b18f43b578 Binary files /dev/null and b/com.unity.ml-agents/Runtime/SensorHelper.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Sensors.meta b/com.unity.ml-agents/Runtime/Sensors.meta new file mode 100644 index 0000000000000000000000000000000000000000..06dbaab14820fb87e11ebda54b8d459604b4d8c9 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Sensors.meta differ diff --git a/com.unity.ml-agents/Runtime/Sensors/ArticulationBodyJointExtractor.cs b/com.unity.ml-agents/Runtime/Sensors/ArticulationBodyJointExtractor.cs new file mode 100644 index 0000000000000000000000000000000000000000..b3b8b7437f964d770ff6ce818929676551492f85 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Sensors/ArticulationBodyJointExtractor.cs @@ -0,0 +1,147 @@ +#if UNITY_2020_1_OR_NEWER + +using System.Collections.Generic; +using UnityEngine; + +namespace Unity.MLAgents.Sensors +{ + [UnityEngine.Scripting.APIUpdating.MovedFrom("Unity.MLAgents.Extensions.Sensors")] + public class ArticulationBodyJointExtractor : IJointExtractor + { + ArticulationBody m_Body; + + public ArticulationBodyJointExtractor(ArticulationBody body) + { + m_Body = body; + } + + public int NumObservations(PhysicsSensorSettings settings) + { + return NumObservations(m_Body, settings); + } + + public static int NumObservations(ArticulationBody body, PhysicsSensorSettings settings) + { + if (body == null || body.isRoot) + { + return 0; + } + + var totalCount = 0; + if (settings.UseJointPositionsAndAngles) + { + switch (body.jointType) + { + case ArticulationJointType.RevoluteJoint: + case ArticulationJointType.SphericalJoint: + // Both RevoluteJoint and SphericalJoint have all angular components. + // We use sine and cosine of the angles for the observations. + totalCount += 2 * body.dofCount; + break; + case ArticulationJointType.FixedJoint: + // Since FixedJoint can't moved, there aren't any interesting observations for it. + break; + case ArticulationJointType.PrismaticJoint: + // One linear component + totalCount += body.dofCount; + break; + } + } + + if (settings.UseJointForces) + { + totalCount += body.dofCount; + } + + return totalCount; + } + + public int Write(PhysicsSensorSettings settings, ObservationWriter writer, int offset) + { + if (m_Body == null || m_Body.isRoot) + { + return 0; + } + + var currentOffset = offset; + + // Write joint positions + if (settings.UseJointPositionsAndAngles) + { + switch (m_Body.jointType) + { + case ArticulationJointType.RevoluteJoint: + case ArticulationJointType.SphericalJoint: + // All joint positions are angular + for (var dofIndex = 0; dofIndex < m_Body.dofCount; dofIndex++) + { + var jointRotationRads = m_Body.jointPosition[dofIndex]; + writer[currentOffset++] = Mathf.Sin(jointRotationRads); + writer[currentOffset++] = Mathf.Cos(jointRotationRads); + } + break; + case ArticulationJointType.FixedJoint: + // No observations + break; + case ArticulationJointType.PrismaticJoint: + writer[currentOffset++] = GetPrismaticValue(); + break; + } + } + + if (settings.UseJointForces) + { + for (var dofIndex = 0; dofIndex < m_Body.dofCount; dofIndex++) + { + // take tanh to keep in [-1, 1] + writer[currentOffset++] = (float)System.Math.Tanh(m_Body.jointForce[dofIndex]); + } + } + + return currentOffset - offset; + } + + float GetPrismaticValue() + { + // Prismatic joints should have at most one free axis. + bool limited = false; + var drive = m_Body.xDrive; + if (m_Body.linearLockX == ArticulationDofLock.LimitedMotion) + { + drive = m_Body.xDrive; + limited = true; + } + else if (m_Body.linearLockY == ArticulationDofLock.LimitedMotion) + { + drive = m_Body.yDrive; + limited = true; + } + else if (m_Body.linearLockZ == ArticulationDofLock.LimitedMotion) + { + drive = m_Body.zDrive; + limited = true; + } + + var jointPos = m_Body.jointPosition[0]; + if (limited) + { + // If locked, interpolate between the limits. + var upperLimit = drive.upperLimit; + var lowerLimit = drive.lowerLimit; + if (upperLimit <= lowerLimit) + { + // Invalid limits (probably equal), so don't try to lerp + return 0; + } + var invLerped = Mathf.InverseLerp(lowerLimit, upperLimit, jointPos); + + // Convert [0, 1] -> [-1, 1] + var normalized = 2.0f * invLerped - 1.0f; + return normalized; + } + // take tanh() to keep in [-1, 1] + return (float)System.Math.Tanh(jointPos); + } + } +} +#endif diff --git a/com.unity.ml-agents/Runtime/Sensors/ArticulationBodyJointExtractor.cs.meta b/com.unity.ml-agents/Runtime/Sensors/ArticulationBodyJointExtractor.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..8b5c4d67292348ad12ac70f08e211b99afccd64e Binary files /dev/null and b/com.unity.ml-agents/Runtime/Sensors/ArticulationBodyJointExtractor.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Sensors/ArticulationBodyPoseExtractor.cs b/com.unity.ml-agents/Runtime/Sensors/ArticulationBodyPoseExtractor.cs new file mode 100644 index 0000000000000000000000000000000000000000..6467948cab0cef5e2fc0a4e8ba7633489cab3897 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Sensors/ArticulationBodyPoseExtractor.cs @@ -0,0 +1,108 @@ +#if UNITY_2020_1_OR_NEWER + +using System.Collections.Generic; +using UnityEngine; + +namespace Unity.MLAgents.Sensors +{ + /// + /// Utility class to track a hierarchy of ArticulationBodies. + /// + [UnityEngine.Scripting.APIUpdating.MovedFrom("Unity.MLAgents.Extensions.Sensors")] + public class ArticulationBodyPoseExtractor : PoseExtractor + { + ArticulationBody[] m_Bodies; + + public ArticulationBodyPoseExtractor(ArticulationBody rootBody) + { + if (rootBody == null) + { + return; + } + + if (!rootBody.isRoot) + { + Debug.Log("Must pass ArticulationBody.isRoot"); + return; + } + + var bodies = rootBody.GetComponentsInChildren(); + if (bodies[0] != rootBody) + { + Debug.Log("Expected root body at index 0"); + return; + } + + var numBodies = bodies.Length; + m_Bodies = bodies; + int[] parentIndices = new int[numBodies]; + parentIndices[0] = -1; + + var bodyToIndex = new Dictionary(); + for (var i = 0; i < numBodies; i++) + { + bodyToIndex[m_Bodies[i]] = i; + } + + for (var i = 1; i < numBodies; i++) + { + var currentArticBody = m_Bodies[i]; + // Component.GetComponentInParent will consider the provided object as well. + // So start looking from the parent. + var currentGameObject = currentArticBody.gameObject; + var parentGameObject = currentGameObject.transform.parent; + var parentArticBody = parentGameObject.GetComponentInParent(); + parentIndices[i] = bodyToIndex[parentArticBody]; + } + + Setup(parentIndices); + } + + /// + protected internal override Vector3 GetLinearVelocityAt(int index) + { + return m_Bodies[index].linearVelocity; + } + + /// + protected internal override Pose GetPoseAt(int index) + { + var body = m_Bodies[index]; + var go = body.gameObject; + var t = go.transform; + return new Pose { rotation = t.rotation, position = t.position }; + } + + /// + protected internal override Object GetObjectAt(int index) + { + return m_Bodies[index]; + } + + internal ArticulationBody[] Bodies => m_Bodies; + + internal IEnumerable GetEnabledArticulationBodies() + { + if (m_Bodies == null) + { + yield break; + } + + for (var i = 0; i < m_Bodies.Length; i++) + { + var articBody = m_Bodies[i]; + if (articBody == null) + { + // Ignore a virtual root. + continue; + } + + if (IsPoseEnabled(i)) + { + yield return articBody; + } + } + } + } +} +#endif // UNITY_2020_1_OR_NEWER diff --git a/com.unity.ml-agents/Runtime/Sensors/ArticulationBodyPoseExtractor.cs.meta b/com.unity.ml-agents/Runtime/Sensors/ArticulationBodyPoseExtractor.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..41f7baad442098a16937ca2df86a169580eba9bf Binary files /dev/null and b/com.unity.ml-agents/Runtime/Sensors/ArticulationBodyPoseExtractor.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Sensors/ArticulationBodySensorComponent.cs b/com.unity.ml-agents/Runtime/Sensors/ArticulationBodySensorComponent.cs new file mode 100644 index 0000000000000000000000000000000000000000..00ccca5e07db50909c79511c66185412d0c5c13d --- /dev/null +++ b/com.unity.ml-agents/Runtime/Sensors/ArticulationBodySensorComponent.cs @@ -0,0 +1,25 @@ +#if UNITY_2020_1_OR_NEWER +using UnityEngine; + +namespace Unity.MLAgents.Sensors +{ + [UnityEngine.Scripting.APIUpdating.MovedFrom("Unity.MLAgents.Extensions.Sensors")] + public class ArticulationBodySensorComponent : SensorComponent + { + public ArticulationBody RootBody; + + [SerializeField] + public PhysicsSensorSettings Settings = PhysicsSensorSettings.Default(); + public string sensorName; + + /// + /// Creates a PhysicsBodySensor. + /// + /// Corresponding sensors. + public override ISensor[] CreateSensors() + { + return new ISensor[] {new PhysicsBodySensor(RootBody, Settings, sensorName)}; + } + } +} +#endif // UNITY_2020_1_OR_NEWER diff --git a/com.unity.ml-agents/Runtime/Sensors/ArticulationBodySensorComponent.cs.meta b/com.unity.ml-agents/Runtime/Sensors/ArticulationBodySensorComponent.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..3cdd83ac522d890f5525e39da17649c3c453adac Binary files /dev/null and b/com.unity.ml-agents/Runtime/Sensors/ArticulationBodySensorComponent.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Sensors/BoxOverlapChecker.cs b/com.unity.ml-agents/Runtime/Sensors/BoxOverlapChecker.cs new file mode 100644 index 0000000000000000000000000000000000000000..c73c36015d0ffd1fb9b834cc4a9e7bd802e82617 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Sensors/BoxOverlapChecker.cs @@ -0,0 +1,264 @@ +using System; +using UnityEngine; + +namespace Unity.MLAgents.Sensors +{ + /// + /// The grid perception strategy that uses box overlap to detect objects. + /// + internal class BoxOverlapChecker : IGridPerception + { + Vector3 m_CellScale; + Vector3Int m_GridSize; + bool m_RotateWithAgent; + LayerMask m_ColliderMask; + GameObject m_CenterObject; + GameObject m_AgentGameObject; + string[] m_DetectableTags; + int m_InitialColliderBufferSize; + int m_MaxColliderBufferSize; + + int m_NumCells; + Vector3 m_HalfCellScale; + Vector3 m_CellCenterOffset; + Vector3[] m_CellLocalPositions; + +#if MLA_UNITY_PHYSICS_MODULE + Collider[] m_ColliderBuffer; + + public event Action GridOverlapDetectedAll; + public event Action GridOverlapDetectedClosest; + public event Action GridOverlapDetectedDebug; +#endif + + public BoxOverlapChecker( + Vector3 cellScale, + Vector3Int gridSize, + bool rotateWithAgent, + LayerMask colliderMask, + GameObject centerObject, + GameObject agentGameObject, + string[] detectableTags, + int initialColliderBufferSize, + int maxColliderBufferSize) + { + m_CellScale = cellScale; + m_GridSize = gridSize; + m_RotateWithAgent = rotateWithAgent; + m_ColliderMask = colliderMask; + m_CenterObject = centerObject; + m_AgentGameObject = agentGameObject; + m_DetectableTags = detectableTags; + m_InitialColliderBufferSize = initialColliderBufferSize; + m_MaxColliderBufferSize = maxColliderBufferSize; + + m_NumCells = gridSize.x * gridSize.z; + m_HalfCellScale = new Vector3(cellScale.x / 2f, cellScale.y, cellScale.z / 2f); + m_CellCenterOffset = new Vector3((gridSize.x - 1f) / 2, 0, (gridSize.z - 1f) / 2); +#if MLA_UNITY_PHYSICS_MODULE + m_ColliderBuffer = new Collider[Math.Min(m_MaxColliderBufferSize, m_InitialColliderBufferSize)]; +#endif + + InitCellLocalPositions(); + } + + public bool RotateWithAgent + { + get { return m_RotateWithAgent; } + set { m_RotateWithAgent = value; } + } + + public LayerMask ColliderMask + { + get { return m_ColliderMask; } + set { m_ColliderMask = value; } + } + + /// + /// Initializes the local location of the cells + /// + void InitCellLocalPositions() + { + m_CellLocalPositions = new Vector3[m_NumCells]; + + for (int i = 0; i < m_NumCells; i++) + { + m_CellLocalPositions[i] = GetCellLocalPosition(i); + } + } + + public Vector3 GetCellLocalPosition(int cellIndex) + { + float x = (cellIndex / m_GridSize.z - m_CellCenterOffset.x) * m_CellScale.x; + float z = (cellIndex % m_GridSize.z - m_CellCenterOffset.z) * m_CellScale.z; + return new Vector3(x, 0, z); + } + + public Vector3 GetCellGlobalPosition(int cellIndex) + { + if (m_RotateWithAgent) + { + return m_CenterObject.transform.TransformPoint(m_CellLocalPositions[cellIndex]); + } + else + { + return m_CellLocalPositions[cellIndex] + m_CenterObject.transform.position; + } + } + + public Quaternion GetGridRotation() + { + return m_RotateWithAgent ? m_CenterObject.transform.rotation : Quaternion.identity; + } + + public void Perceive() + { +#if MLA_UNITY_PHYSICS_MODULE + for (var cellIndex = 0; cellIndex < m_NumCells; cellIndex++) + { + var cellCenter = GetCellGlobalPosition(cellIndex); + var numFound = BufferResizingOverlapBoxNonAlloc(cellCenter, m_HalfCellScale, GetGridRotation()); + + if (GridOverlapDetectedAll != null) + { + ParseCollidersAll(m_ColliderBuffer, numFound, cellIndex, cellCenter, GridOverlapDetectedAll); + } + if (GridOverlapDetectedClosest != null) + { + ParseCollidersClosest(m_ColliderBuffer, numFound, cellIndex, cellCenter, GridOverlapDetectedClosest); + } + } +#endif + } + + public void UpdateGizmo() + { +#if MLA_UNITY_PHYSICS_MODULE + for (var cellIndex = 0; cellIndex < m_NumCells; cellIndex++) + { + var cellCenter = GetCellGlobalPosition(cellIndex); + var numFound = BufferResizingOverlapBoxNonAlloc(cellCenter, m_HalfCellScale, GetGridRotation()); + + ParseCollidersClosest(m_ColliderBuffer, numFound, cellIndex, cellCenter, GridOverlapDetectedDebug); + } +#endif + } + +#if MLA_UNITY_PHYSICS_MODULE + /// + /// This method attempts to perform the Physics.OverlapBoxNonAlloc and will double the size of the Collider buffer + /// if the number of Colliders in the buffer after the call is equal to the length of the buffer. + /// + /// + /// + /// + /// Found number of overlapping boxes. + int BufferResizingOverlapBoxNonAlloc(Vector3 cellCenter, Vector3 halfCellScale, Quaternion rotation) + { + int numFound; + // Since we can only get a fixed number of results, requery + // until we're sure we can hold them all (or until we hit the max size). + while (true) + { + numFound = Physics.OverlapBoxNonAlloc(cellCenter, halfCellScale, m_ColliderBuffer, rotation, m_ColliderMask); + if (numFound == m_ColliderBuffer.Length && m_ColliderBuffer.Length < m_MaxColliderBufferSize) + { + m_ColliderBuffer = new Collider[Math.Min(m_MaxColliderBufferSize, m_ColliderBuffer.Length * 2)]; + m_InitialColliderBufferSize = m_ColliderBuffer.Length; + } + else + { + break; + } + } + return numFound; + } + + /// + /// Parses the array of colliders found within a cell. Finds the closest gameobject to the agent root reference within the cell + /// + void ParseCollidersClosest(Collider[] foundColliders, int numFound, int cellIndex, Vector3 cellCenter, Action detectedAction) + { + GameObject closestColliderGo = null; + var minDistanceSquared = float.MaxValue; + + for (var i = 0; i < numFound; i++) + { + var currentColliderGo = foundColliders[i].gameObject; + + // Continue if the current collider go is the root reference + if (ReferenceEquals(currentColliderGo, m_AgentGameObject)) + { + continue; + } + + var closestColliderPoint = foundColliders[i].ClosestPointOnBounds(cellCenter); + var currentDistanceSquared = (closestColliderPoint - m_CenterObject.transform.position).sqrMagnitude; + + if (currentDistanceSquared >= minDistanceSquared) + { + continue; + } + + // Checks if our colliders contain a detectable object + var index = -1; + for (var ii = 0; ii < m_DetectableTags.Length; ii++) + { + if (currentColliderGo.CompareTag(m_DetectableTags[ii])) + { + index = ii; + break; + } + } + if (index > -1 && currentDistanceSquared < minDistanceSquared) + { + minDistanceSquared = currentDistanceSquared; + closestColliderGo = currentColliderGo; + } + } + + if (!ReferenceEquals(closestColliderGo, null)) + { + detectedAction.Invoke(closestColliderGo, cellIndex); + } + } + + /// + /// Parses all colliders in the array of colliders found within a cell. + /// + void ParseCollidersAll(Collider[] foundColliders, int numFound, int cellIndex, Vector3 cellCenter, Action detectedAction) + { + for (int i = 0; i < numFound; i++) + { + var currentColliderGo = foundColliders[i].gameObject; + if (!ReferenceEquals(currentColliderGo, m_AgentGameObject)) + { + detectedAction.Invoke(currentColliderGo, cellIndex); + } + } + } + +#endif + + public void RegisterSensor(GridSensorBase sensor) + { +#if MLA_UNITY_PHYSICS_MODULE + if (sensor.GetProcessCollidersMethod() == ProcessCollidersMethod.ProcessAllColliders) + { + GridOverlapDetectedAll += sensor.ProcessDetectedObject; + } + else + { + GridOverlapDetectedClosest += sensor.ProcessDetectedObject; + } +#endif + } + + public void RegisterDebugSensor(GridSensorBase debugSensor) + { +#if MLA_UNITY_PHYSICS_MODULE + GridOverlapDetectedDebug += debugSensor.ProcessDetectedObject; +#endif + } + } +} diff --git a/com.unity.ml-agents/Runtime/Sensors/BoxOverlapChecker.cs.meta b/com.unity.ml-agents/Runtime/Sensors/BoxOverlapChecker.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..1d20815c0ec63004e0972c2906c030cee0287a32 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Sensors/BoxOverlapChecker.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Sensors/BufferSensor.cs b/com.unity.ml-agents/Runtime/Sensors/BufferSensor.cs new file mode 100644 index 0000000000000000000000000000000000000000..3f0f6eef42b6afcc9f3d9f4752a384c2dda916e4 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Sensors/BufferSensor.cs @@ -0,0 +1,123 @@ +using System; + +namespace Unity.MLAgents.Sensors +{ + /// + /// A Sensor that allows to observe a variable number of entities. + /// + public class BufferSensor : ISensor, IBuiltInSensor + { + private string m_Name; + private int m_MaxNumObs; + private int m_ObsSize; + float[] m_ObservationBuffer; + int m_CurrentNumObservables; + ObservationSpec m_ObservationSpec; + + + /// + /// Creates the BufferSensor. + /// + /// The maximum number of observations to be appended to this BufferSensor. + /// The size of each observation appended to the BufferSensor. + /// The name of the sensor. + public BufferSensor(int maxNumberObs, int obsSize, string name) + { + m_Name = name; + m_MaxNumObs = maxNumberObs; + m_ObsSize = obsSize; + m_ObservationBuffer = new float[m_ObsSize * m_MaxNumObs]; + m_CurrentNumObservables = 0; + m_ObservationSpec = ObservationSpec.VariableLength(m_MaxNumObs, m_ObsSize); + } + + /// + public ObservationSpec GetObservationSpec() + { + return m_ObservationSpec; + } + + /// + /// Appends an observation to the buffer. If the buffer is full (maximum number + /// of observation is reached) the observation will be ignored. the length of + /// the provided observation array must be equal to the observation size of + /// the buffer sensor. + /// + /// The float array observation + public void AppendObservation(float[] obs) + { + if (obs.Length != m_ObsSize) + { + throw new UnityAgentsException( + "The BufferSensor was expecting an observation of size " + + $"{m_ObsSize} but received {obs.Length} observations instead." + ); + } + if (m_CurrentNumObservables >= m_MaxNumObs) + { + return; + } + for (int i = 0; i < obs.Length; i++) + { + m_ObservationBuffer[m_CurrentNumObservables * m_ObsSize + i] = obs[i]; + } + m_CurrentNumObservables++; + } + + /// + public int Write(ObservationWriter writer) + { + // for (int i = 0; i < m_ObsSize * m_MaxNumObs; i++) + // { + // writer[i] = m_ObservationBuffer[i]; + // } + + for (int i = 0; i < m_MaxNumObs; i++) + { + for (int j = 0; j < m_ObsSize; j++) + { + writer[i, j] = m_ObservationBuffer[i * m_ObsSize + j]; + } + } + + return m_ObsSize * m_MaxNumObs; + } + + /// + public virtual byte[] GetCompressedObservation() + { + return null; + } + + /// + public void Update() + { + Reset(); + } + + /// + public void Reset() + { + m_CurrentNumObservables = 0; + Array.Clear(m_ObservationBuffer, 0, m_ObservationBuffer.Length); + } + + /// + public CompressionSpec GetCompressionSpec() + { + return CompressionSpec.Default(); + } + + /// + public string GetName() + { + return m_Name; + } + + /// + public BuiltInSensorType GetBuiltInSensorType() + { + return BuiltInSensorType.BufferSensor; + } + } +} diff --git a/com.unity.ml-agents/Runtime/Sensors/BufferSensor.cs.meta b/com.unity.ml-agents/Runtime/Sensors/BufferSensor.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..327456d2ee4613204f2328959a30aa3ab8f12066 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Sensors/BufferSensor.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Sensors/BufferSensorComponent.cs b/com.unity.ml-agents/Runtime/Sensors/BufferSensorComponent.cs new file mode 100644 index 0000000000000000000000000000000000000000..8be049e8a2dc1ad918b65f8f79cdc8211af7691f --- /dev/null +++ b/com.unity.ml-agents/Runtime/Sensors/BufferSensorComponent.cs @@ -0,0 +1,68 @@ +using UnityEngine; + +namespace Unity.MLAgents.Sensors +{ + /// + /// A SensorComponent that creates a . + /// + [AddComponentMenu("ML Agents/Buffer Sensor", (int)MenuGroup.Sensors)] + public class BufferSensorComponent : SensorComponent + { + /// + /// Name of the generated object. + /// Note that changing this at runtime does not affect how the Agent sorts the sensors. + /// + public string SensorName + { + get { return m_SensorName; } + set { m_SensorName = value; } + } + [HideInInspector, SerializeField] + private string m_SensorName = "BufferSensor"; + + /// + /// This is how many floats each entities will be represented with. This number + /// is fixed and all entities must have the same representation. + /// + public int ObservableSize + { + get { return m_ObservableSize; } + set { m_ObservableSize = value; } + } + [HideInInspector, SerializeField] + private int m_ObservableSize; + + /// + /// This is the maximum number of entities the `BufferSensor` will be able to + /// collect. + /// + public int MaxNumObservables + { + get { return m_MaxNumObservables; } + set { m_MaxNumObservables = value; } + } + [HideInInspector, SerializeField] + private int m_MaxNumObservables; + + private BufferSensor m_Sensor; + + /// + public override ISensor[] CreateSensors() + { + m_Sensor = new BufferSensor(MaxNumObservables, ObservableSize, m_SensorName); + return new ISensor[] { m_Sensor }; + } + + /// + /// Appends an observation to the buffer. If the buffer is full (maximum number + /// of observation is reached) the observation will be ignored. the length of + /// the provided observation array must be equal to the observation size of + /// the buffer sensor. + /// + /// The float array observation + public void AppendObservation(float[] obs) + { + m_Sensor.AppendObservation(obs); + } + } +} diff --git a/com.unity.ml-agents/Runtime/Sensors/BufferSensorComponent.cs.meta b/com.unity.ml-agents/Runtime/Sensors/BufferSensorComponent.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..69bee5ca08963d2114010ba87de1b28487d0929d Binary files /dev/null and b/com.unity.ml-agents/Runtime/Sensors/BufferSensorComponent.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Sensors/CameraSensor.cs b/com.unity.ml-agents/Runtime/Sensors/CameraSensor.cs new file mode 100644 index 0000000000000000000000000000000000000000..f4e9275d4b555311f4825ddfc88e41c4c8cb8b4c --- /dev/null +++ b/com.unity.ml-agents/Runtime/Sensors/CameraSensor.cs @@ -0,0 +1,185 @@ +using System; +using UnityEngine; +using UnityEngine.Rendering; + +namespace Unity.MLAgents.Sensors +{ + /// + /// A sensor that wraps a Camera object to generate visual observations for an agent. + /// + public class CameraSensor : ISensor, IBuiltInSensor, IDisposable + { + Camera m_Camera; + int m_Width; + int m_Height; + bool m_Grayscale; + string m_Name; + private ObservationSpec m_ObservationSpec; + SensorCompressionType m_CompressionType; + Texture2D m_Texture; + + /// + /// The Camera used for rendering the sensor observations. + /// + public Camera Camera + { + get { return m_Camera; } + set { m_Camera = value; } + } + + /// + /// The compression type used by the sensor. + /// + public SensorCompressionType CompressionType + { + get { return m_CompressionType; } + set { m_CompressionType = value; } + } + + /// + /// Creates and returns the camera sensor. + /// + /// Camera object to capture images from. + /// The width of the generated visual observation. + /// The height of the generated visual observation. + /// Whether to convert the generated image to grayscale or keep color. + /// The name of the camera sensor. + /// The compression to apply to the generated image. + /// The type of observation. + public CameraSensor( + Camera camera, int width, int height, bool grayscale, string name, SensorCompressionType compression, ObservationType observationType = ObservationType.Default) + { + m_Camera = camera; + m_Width = width; + m_Height = height; + m_Grayscale = grayscale; + m_Name = name; + var channels = grayscale ? 1 : 3; + m_ObservationSpec = ObservationSpec.Visual(channels, height, width, observationType); + m_CompressionType = compression; + m_Texture = new Texture2D(width, height, TextureFormat.RGB24, false); + } + + /// + /// Accessor for the name of the sensor. + /// + /// Sensor name. + public string GetName() + { + return m_Name; + } + + /// + /// Returns a description of the observations that will be generated by the sensor. + /// The shape will be 1 x h x w for grayscale and 3 x h x w for color. + /// The dimensions have translational equivariance along width and height, + /// and no property along the channels dimension. + /// + /// The `ObservationSpec`. + public ObservationSpec GetObservationSpec() + { + return m_ObservationSpec; + } + + /// + /// Generates a compressed image. This can be valuable in speeding-up training. + /// + /// Compressed image. + public byte[] GetCompressedObservation() + { + using (TimerStack.Instance.Scoped("CameraSensor.GetCompressedObservation")) + { + // TODO support more types here, e.g. JPG + var compressed = m_Texture.EncodeToPNG(); + return compressed; + } + } + + /// + /// Writes out the generated, uncompressed image to the provided . + /// + /// Where the observation is written to. + /// The number of elements written. + public int Write(ObservationWriter writer) + { + using (TimerStack.Instance.Scoped("CameraSensor.WriteToTensor")) + { + var numWritten = writer.WriteTexture(m_Texture, m_Grayscale); + return numWritten; + } + } + + /// + public void Update() + { + ObservationToTexture(m_Camera, m_Texture, m_Width, m_Height); + } + + /// + public void Reset() { } + + /// + public CompressionSpec GetCompressionSpec() + { + return new CompressionSpec(m_CompressionType); + } + + /// + /// Renders a Camera instance to a 2D texture at the corresponding resolution. + /// + /// Camera. + /// Texture2D to render to. + /// Width of resulting 2D texture. + /// Height of resulting 2D texture. + public static void ObservationToTexture(Camera obsCamera, Texture2D texture2D, int width, int height) + { + if (SystemInfo.graphicsDeviceType == GraphicsDeviceType.Null) + { + Debug.LogError("GraphicsDeviceType is Null. This will likely crash when trying to render."); + } + + var oldRec = obsCamera.rect; + obsCamera.rect = new Rect(0f, 0f, 1f, 1f); + var depth = 24; + var format = RenderTextureFormat.Default; + var readWrite = RenderTextureReadWrite.Default; + + var tempRt = + RenderTexture.GetTemporary(width, height, depth, format, readWrite); + + var prevActiveRt = RenderTexture.active; + var prevCameraRt = obsCamera.targetTexture; + + // render to offscreen texture (readonly from CPU side) + RenderTexture.active = tempRt; + obsCamera.targetTexture = tempRt; + + obsCamera.Render(); + + texture2D.ReadPixels(new Rect(0, 0, texture2D.width, texture2D.height), 0, 0); + + obsCamera.targetTexture = prevCameraRt; + obsCamera.rect = oldRec; + RenderTexture.active = prevActiveRt; + RenderTexture.ReleaseTemporary(tempRt); + } + + /// + public BuiltInSensorType GetBuiltInSensorType() + { + return BuiltInSensorType.CameraSensor; + } + + /// + /// Clean up the owned Texture2D. + /// + public void Dispose() + { + if (!ReferenceEquals(null, m_Texture)) + { + Utilities.DestroyTexture(m_Texture); + m_Texture = null; + } + } + } +} diff --git a/com.unity.ml-agents/Runtime/Sensors/CameraSensor.cs.meta b/com.unity.ml-agents/Runtime/Sensors/CameraSensor.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..1a0314b8f776bfc2ec2ef60f4a0cf9cbb5ec419c Binary files /dev/null and b/com.unity.ml-agents/Runtime/Sensors/CameraSensor.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Sensors/CameraSensorComponent.cs b/com.unity.ml-agents/Runtime/Sensors/CameraSensorComponent.cs new file mode 100644 index 0000000000000000000000000000000000000000..f6b53f087eaa062308dea49c074893d53df32011 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Sensors/CameraSensorComponent.cs @@ -0,0 +1,179 @@ +using System; +using UnityEngine; +using UnityEngine.Serialization; + +namespace Unity.MLAgents.Sensors +{ + /// + /// A SensorComponent that creates a . + /// + [AddComponentMenu("ML Agents/Camera Sensor", (int)MenuGroup.Sensors)] + public class CameraSensorComponent : SensorComponent, IDisposable + { + [HideInInspector, SerializeField, FormerlySerializedAs("camera")] + Camera m_Camera; + + CameraSensor m_Sensor; + + /// + /// Camera object that provides the data to the sensor. + /// + public Camera Camera + { + get { return m_Camera; } + set { m_Camera = value; UpdateSensor(); } + } + + [HideInInspector, SerializeField, FormerlySerializedAs("sensorName")] + string m_SensorName = "CameraSensor"; + + /// + /// Name of the generated object. + /// Note that changing this at runtime does not affect how the Agent sorts the sensors. + /// + public string SensorName + { + get { return m_SensorName; } + set { m_SensorName = value; } + } + + [HideInInspector, SerializeField, FormerlySerializedAs("width")] + int m_Width = 84; + + /// + /// Width of the generated observation. + /// Note that changing this after the sensor is created has no effect. + /// + public int Width + { + get { return m_Width; } + set { m_Width = value; } + } + + [HideInInspector, SerializeField, FormerlySerializedAs("height")] + int m_Height = 84; + + /// + /// Height of the generated observation. + /// Note that changing this after the sensor is created has no effect. + /// + public int Height + { + get { return m_Height; } + set { m_Height = value; } + } + + [HideInInspector, SerializeField, FormerlySerializedAs("grayscale")] + bool m_Grayscale; + + /// + /// Whether to generate grayscale images or color. + /// Note that changing this after the sensor is created has no effect. + /// + public bool Grayscale + { + get { return m_Grayscale; } + set { m_Grayscale = value; } + } + + [HideInInspector, SerializeField] + ObservationType m_ObservationType; + + /// + /// The type of the observation. + /// + public ObservationType ObservationType + { + get { return m_ObservationType; } + set { m_ObservationType = value; UpdateSensor(); } + } + + [HideInInspector, SerializeField] + bool m_RuntimeCameraEnable; + + + /// + /// Controls the whether the camera sensor's attached camera + /// is enabled during runtime. Overrides the camera object enabled status. + /// Disabled for improved performance. Disabled by default. + /// + public bool RuntimeCameraEnable + { + get { return m_RuntimeCameraEnable; } + set { m_RuntimeCameraEnable = value; UpdateSensor(); } + } + + [HideInInspector, SerializeField] + [Range(1, 50)] + [Tooltip("Number of camera frames that will be stacked before being fed to the neural network.")] + int m_ObservationStacks = 1; + + [HideInInspector, SerializeField, FormerlySerializedAs("compression")] + SensorCompressionType m_Compression = SensorCompressionType.PNG; + + /// + /// The compression type to use for the sensor. + /// + public SensorCompressionType CompressionType + { + get { return m_Compression; } + set { m_Compression = value; UpdateSensor(); } + } + + /// + /// Whether to stack previous observations. Using 1 means no previous observations. + /// Note that changing this after the sensor is created has no effect. + /// + public int ObservationStacks + { + get { return m_ObservationStacks; } + set { m_ObservationStacks = value; } + } + + void Start() + { + UpdateSensor(); + } + + /// + /// Creates the + /// + /// The created object for this component. + public override ISensor[] CreateSensors() + { + Dispose(); + m_Sensor = new CameraSensor(m_Camera, m_Width, m_Height, Grayscale, m_SensorName, m_Compression, m_ObservationType); + + if (ObservationStacks != 1) + { + return new ISensor[] { new StackingSensor(m_Sensor, ObservationStacks) }; + } + return new ISensor[] { m_Sensor }; + } + + /// + /// Update fields that are safe to change on the Sensor at runtime. + /// + internal void UpdateSensor() + { + if (m_Sensor != null) + { + m_Sensor.Camera = m_Camera; + m_Sensor.CompressionType = m_Compression; + m_Sensor.Camera.enabled = m_RuntimeCameraEnable; + } + } + + /// + /// Clean up the sensor created by CreateSensors(). + /// + public void Dispose() + { + if (!ReferenceEquals(m_Sensor, null)) + { + m_Sensor.Dispose(); + m_Sensor = null; + } + } + } +} diff --git a/com.unity.ml-agents/Runtime/Sensors/CameraSensorComponent.cs.meta b/com.unity.ml-agents/Runtime/Sensors/CameraSensorComponent.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..307dc6495244fec3cb7e340d0cad52ce036a411b Binary files /dev/null and b/com.unity.ml-agents/Runtime/Sensors/CameraSensorComponent.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Sensors/CompressionSpec.cs b/com.unity.ml-agents/Runtime/Sensors/CompressionSpec.cs new file mode 100644 index 0000000000000000000000000000000000000000..52248b26094ee8ed714f9b1bfc53759af6be8cf2 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Sensors/CompressionSpec.cs @@ -0,0 +1,113 @@ +using System.Linq; +namespace Unity.MLAgents.Sensors +{ + /// + /// The compression setting for visual/camera observations. + /// + public enum SensorCompressionType + { + /// + /// No compression. Data is preserved as float arrays. + /// + None, + + /// + /// PNG format. Data will be stored in binary format. + /// + PNG + } + + /// + /// A description of the compression used for observations. + /// + /// + /// Most ISensor implementations can't take advantage of compression, + /// and should return CompressionSpec.Default() from their ISensor.GetCompressionSpec() methods. + /// Visual observations, or mulitdimensional categorical observations (for example, image segmentation + /// or the piece types in a match-3 game board) can use PNG compression reduce the amount of + /// data transferred between Unity and the trainer. + /// + public struct CompressionSpec + { + internal SensorCompressionType m_SensorCompressionType; + + /// + /// The compression type that the sensor will use for its observations. + /// + public SensorCompressionType SensorCompressionType + { + get => m_SensorCompressionType; + } + + internal int[] m_CompressedChannelMapping; + + /// + /// The mapping of the channels in compressed data to the actual channel after decompression. + /// + /// + /// The mapping is a list of integer index with the same length as + /// the number of output observation layers (channels), including padding if there's any. + /// Each index indicates the actual channel the layer will go into. + /// Layers with the same index will be averaged, and layers with negative index will be dropped. + /// For example, mapping for CameraSensor using grayscale and stacking of two: [0, 0, 0, 1, 1, 1] + /// Mapping for GridSensor of 4 channels and stacking of two: [0, 1, 2, 3, -1, -1, 4, 5, 6, 7, -1, -1] + /// + public int[] CompressedChannelMapping + { + get => m_CompressedChannelMapping; + } + + /// + /// Return a CompressionSpec indicating possible compression. + /// + /// The compression type to use. + /// Optional mapping mapping of the channels in compressed data to the + /// actual channel after decompression. + public CompressionSpec(SensorCompressionType sensorCompressionType, int[] compressedChannelMapping = null) + { + m_SensorCompressionType = sensorCompressionType; + m_CompressedChannelMapping = compressedChannelMapping; + } + + /// + /// Return a CompressionSpec indicating no compression. This is recommended for most sensors. + /// + /// `CompressionSpec` indicating no compression. + public static CompressionSpec Default() + { + return new CompressionSpec + { + m_SensorCompressionType = SensorCompressionType.None, + m_CompressedChannelMapping = null + }; + } + + /// + /// Return whether the compressed channel mapping is "trivial"; if so it doesn't need to be sent to the + /// trainer. + /// + /// True if the compressed channel mapping is trivial, False if not. + internal bool IsTrivialMapping() + { + var mapping = CompressedChannelMapping; + if (mapping == null) + { + return true; + } + // check if mapping equals zero mapping + if (mapping.Length == 3 && mapping.All(m => m == 0)) + { + return true; + } + // check if mapping equals identity mapping + for (var i = 0; i < mapping.Length; i++) + { + if (mapping[i] != i) + { + return false; + } + } + return true; + } + } +} diff --git a/com.unity.ml-agents/Runtime/Sensors/CompressionSpec.cs.meta b/com.unity.ml-agents/Runtime/Sensors/CompressionSpec.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..3bbac496d7e37c0973b07b2b880f8a645394a9d4 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Sensors/CompressionSpec.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Sensors/CountingGridSensor.cs b/com.unity.ml-agents/Runtime/Sensors/CountingGridSensor.cs new file mode 100644 index 0000000000000000000000000000000000000000..b3088b70f3791e05e0c76c1563eebee1b872d6fb --- /dev/null +++ b/com.unity.ml-agents/Runtime/Sensors/CountingGridSensor.cs @@ -0,0 +1,61 @@ +using UnityEngine; + +namespace Unity.MLAgents.Sensors +{ + /// + /// Grid-based sensor that counts the number of detctable objects. + /// + [UnityEngine.Scripting.APIUpdating.MovedFrom("Unity.MLAgents.Extensions.Sensors")] + public class CountingGridSensor : GridSensorBase + { + /// + /// Create a CountingGridSensor with the specified configuration. + /// + /// The sensor name + /// The scale of each cell in the grid + /// Number of cells on each side of the grid + /// Tags to be detected by the sensor + /// Compression type + public CountingGridSensor( + string name, + Vector3 cellScale, + Vector3Int gridSize, + string[] detectableTags, + SensorCompressionType compression + ) : base(name, cellScale, gridSize, detectableTags, compression) + { + CompressionType = SensorCompressionType.None; + } + + /// + protected override int GetCellObservationSize() + { + return DetectableTags == null ? 0 : DetectableTags.Length; + } + + /// + protected override bool IsDataNormalized() + { + return false; + } + + /// + protected internal override ProcessCollidersMethod GetProcessCollidersMethod() + { + return ProcessCollidersMethod.ProcessAllColliders; + } + + /// + /// Get object counts for each detectable tags detected in a cell. + /// + /// The game object that was detected within a certain cell + /// The index of the detectedObject's tag in the DetectableObjects list + /// The buffer to write the observation values. + /// The buffer size is configured by . + /// + protected override void GetObjectData(GameObject detectedObject, int tagIndex, float[] dataBuffer) + { + dataBuffer[tagIndex] += 1; + } + } +} diff --git a/com.unity.ml-agents/Runtime/Sensors/CountingGridSensor.cs.meta b/com.unity.ml-agents/Runtime/Sensors/CountingGridSensor.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..853e1b61452397a8b64a1a0bd48efd0c1ce91652 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Sensors/CountingGridSensor.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Sensors/GridSensorBase.cs b/com.unity.ml-agents/Runtime/Sensors/GridSensorBase.cs new file mode 100644 index 0000000000000000000000000000000000000000..05325d9207a56f6214b9726e165f30139303a606 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Sensors/GridSensorBase.cs @@ -0,0 +1,350 @@ +using System; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.Profiling; + +namespace Unity.MLAgents.Sensors +{ + /// + /// The way the GridSensor process detected colliders in a cell. + /// + public enum ProcessCollidersMethod + { + /// + /// Get data from all colliders detected in a cell + /// + ProcessAllColliders, + + /// + /// Get data from the collider closest to the agent + /// + ProcessClosestColliders + } + + /// + /// Grid-based sensor. + /// + public class GridSensorBase : ISensor, IBuiltInSensor, IDisposable + { + string m_Name; + Vector3 m_CellScale; + Vector3Int m_GridSize; + string[] m_DetectableTags; + SensorCompressionType m_CompressionType; + ObservationSpec m_ObservationSpec; + internal IGridPerception m_GridPerception; + + // Buffers + float[] m_PerceptionBuffer; + Color[] m_PerceptionColors; + Texture2D m_PerceptionTexture; + float[] m_CellDataBuffer; + + // Utility Constants Calculated on Init + int m_NumCells; + int m_CellObservationSize; + Vector3 m_CellCenterOffset; + + + /// + /// Create a GridSensorBase with the specified configuration. + /// + /// The sensor name + /// The scale of each cell in the grid + /// Number of cells on each side of the grid + /// Tags to be detected by the sensor + /// Compression type + public GridSensorBase( + string name, + Vector3 cellScale, + Vector3Int gridSize, + string[] detectableTags, + SensorCompressionType compression + ) + { + m_Name = name; + m_CellScale = cellScale; + m_GridSize = gridSize; + m_DetectableTags = detectableTags; + CompressionType = compression; + + if (m_GridSize.y != 1) + { + throw new UnityAgentsException("GridSensor only supports 2D grids."); + } + + m_NumCells = m_GridSize.x * m_GridSize.z; + m_CellObservationSize = GetCellObservationSize(); + m_ObservationSpec = ObservationSpec.Visual(m_CellObservationSize, m_GridSize.x, m_GridSize.z); + m_PerceptionTexture = new Texture2D(m_GridSize.x, m_GridSize.z, TextureFormat.RGB24, false); + + ResetPerceptionBuffer(); + } + + /// + /// The compression type used by the sensor. + /// + public SensorCompressionType CompressionType + { + get { return m_CompressionType; } + set + { + if (!IsDataNormalized() && value == SensorCompressionType.PNG) + { + Debug.LogWarning($"Compression type {value} is only supported with normalized data. " + + "The sensor will not compress the data."); + return; + } + m_CompressionType = value; + } + } + + internal float[] PerceptionBuffer + { + get { return m_PerceptionBuffer; } + } + + /// + /// The tags which the sensor dectects. + /// + protected string[] DetectableTags + { + get { return m_DetectableTags; } + } + + /// + public void Reset() { } + + /// + /// Clears the perception buffer before loading in new data. + /// + public void ResetPerceptionBuffer() + { + if (m_PerceptionBuffer != null) + { + Array.Clear(m_PerceptionBuffer, 0, m_PerceptionBuffer.Length); + Array.Clear(m_CellDataBuffer, 0, m_CellDataBuffer.Length); + } + else + { + m_PerceptionBuffer = new float[m_CellObservationSize * m_NumCells]; + m_CellDataBuffer = new float[m_CellObservationSize]; + m_PerceptionColors = new Color[m_NumCells]; + } + } + + /// + public string GetName() + { + return m_Name; + } + + /// + public CompressionSpec GetCompressionSpec() + { + return new CompressionSpec(CompressionType); + } + + /// + public BuiltInSensorType GetBuiltInSensorType() + { + return BuiltInSensorType.GridSensor; + } + + /// + public byte[] GetCompressedObservation() + { + using (TimerStack.Instance.Scoped("GridSensor.GetCompressedObservation")) + { + var allBytes = new List(); + var numImages = (m_CellObservationSize + 2) / 3; + for (int i = 0; i < numImages; i++) + { + var channelIndex = 3 * i; + GridValuesToTexture(channelIndex, Math.Min(3, m_CellObservationSize - channelIndex)); + allBytes.AddRange(m_PerceptionTexture.EncodeToPNG()); + } + + return allBytes.ToArray(); + } + } + + /// + /// Convert observation values to texture for PNG compression. + /// + void GridValuesToTexture(int channelIndex, int numChannelsToAdd) + { + for (int i = 0; i < m_NumCells; i++) + { + for (int j = 0; j < numChannelsToAdd; j++) + { + m_PerceptionColors[i][j] = m_PerceptionBuffer[i * m_CellObservationSize + channelIndex + j]; + } + } + m_PerceptionTexture.SetPixels(m_PerceptionColors); + } + + /// + /// Get the observation values of the detected game object. + /// Default is to record the detected tag index. + /// + /// This method can be overridden to encode the observation differently or get custom data from the object. + /// When overriding this method, and + /// might also need to change accordingly. + /// + /// The game object that was detected within a certain cell + /// The index of the detectedObject's tag in the DetectableObjects list + /// The buffer to write the observation values. + /// The buffer size is configured by . + /// + /// + /// + /// Here is an example of overriding GetObjectData to get the velocity of a potential Rigidbody: + /// + /// + /// protected override void GetObjectData(GameObject detectedObject, int tagIndex, float[] dataBuffer) + /// { + /// if (tagIndex == Array.IndexOf(DetectableTags, "RigidBodyObject")) + /// { + /// Rigidbody rigidbody = detectedObject.GetComponent<Rigidbody>(); + /// dataBuffer[0] = rigidbody.velocity.x; + /// dataBuffer[1] = rigidbody.velocity.y; + /// dataBuffer[2] = rigidbody.velocity.z; + /// } + /// } + /// + /// + protected virtual void GetObjectData(GameObject detectedObject, int tagIndex, float[] dataBuffer) + { + dataBuffer[0] = tagIndex + 1; + } + + /// + /// Get the observation size for each cell. This will be the size of dataBuffer for . + /// If overriding , override this method as well to the custom observation size. + /// + /// The observation size of each cell. + protected virtual int GetCellObservationSize() + { + return 1; + } + + /// + /// Whether the data is normalized within [0, 1]. The sensor can only use PNG compression if the data is normailzed. + /// If overriding , override this method as well according to the custom observation values. + /// + /// Bool value indicating whether data is normalized. + protected virtual bool IsDataNormalized() + { + return false; + } + + /// + /// Whether to process all detected colliders in a cell. Default to false and only use the one closest to the agent. + /// If overriding , consider override this method when needed. + /// + /// Bool value indicating whether to process all detected colliders in a cell. + protected internal virtual ProcessCollidersMethod GetProcessCollidersMethod() + { + return ProcessCollidersMethod.ProcessClosestColliders; + } + + /// + /// If using PNG compression, check if the values are normalized. + /// + void ValidateValues(float[] dataValues, GameObject detectedObject) + { + if (m_CompressionType != SensorCompressionType.PNG) + { + return; + } + + for (int j = 0; j < dataValues.Length; j++) + { + if (dataValues[j] < 0 || dataValues[j] > 1) + throw new UnityAgentsException($"When using compression type {m_CompressionType} the data value has to be normalized between 0-1. " + + $"Received value[{dataValues[j]}] for {detectedObject.name}"); + } + } + + /// + /// Collect data from the detected object if a detectable tag is matched. + /// + internal void ProcessDetectedObject(GameObject detectedObject, int cellIndex) + { + Profiler.BeginSample("GridSensor.ProcessDetectedObject"); + for (var i = 0; i < m_DetectableTags.Length; i++) + { + if (!ReferenceEquals(detectedObject, null) && detectedObject.CompareTag(m_DetectableTags[i])) + { + if (GetProcessCollidersMethod() == ProcessCollidersMethod.ProcessAllColliders) + { + Array.Copy(m_PerceptionBuffer, cellIndex * m_CellObservationSize, m_CellDataBuffer, 0, m_CellObservationSize); + } + else + { + Array.Clear(m_CellDataBuffer, 0, m_CellDataBuffer.Length); + } + + GetObjectData(detectedObject, i, m_CellDataBuffer); + ValidateValues(m_CellDataBuffer, detectedObject); + Array.Copy(m_CellDataBuffer, 0, m_PerceptionBuffer, cellIndex * m_CellObservationSize, m_CellObservationSize); + break; + } + } + Profiler.EndSample(); + } + + /// + public void Update() + { + ResetPerceptionBuffer(); + using (TimerStack.Instance.Scoped("GridSensor.Update")) + { + if (m_GridPerception != null) + { + m_GridPerception.Perceive(); + } + } + } + + /// + public ObservationSpec GetObservationSpec() + { + return m_ObservationSpec; + } + + /// + public int Write(ObservationWriter writer) + { + using (TimerStack.Instance.Scoped("GridSensor.Write")) + { + int index = 0; + for (var h = m_GridSize.z - 1; h >= 0; h--) + { + for (var w = 0; w < m_GridSize.x; w++) + { + for (var d = 0; d < m_CellObservationSize; d++) + { + writer[d, h, w] = m_PerceptionBuffer[index]; + index++; + } + } + } + return index; + } + } + + /// + /// Clean up the internal objects. + /// + public void Dispose() + { + if (!ReferenceEquals(null, m_PerceptionTexture)) + { + Utilities.DestroyTexture(m_PerceptionTexture); + m_PerceptionTexture = null; + } + } + } +} diff --git a/com.unity.ml-agents/Runtime/Sensors/GridSensorBase.cs.meta b/com.unity.ml-agents/Runtime/Sensors/GridSensorBase.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..623d17202d5056f3cbaa7e2f59c020a0199c85d9 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Sensors/GridSensorBase.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Sensors/GridSensorComponent.cs b/com.unity.ml-agents/Runtime/Sensors/GridSensorComponent.cs new file mode 100644 index 0000000000000000000000000000000000000000..95f255ba5b9fe0f35b12a072e9341cc1af3db8c5 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Sensors/GridSensorComponent.cs @@ -0,0 +1,313 @@ +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace Unity.MLAgents.Sensors +{ + /// + /// A SensorComponent that creates a . + /// + [AddComponentMenu("ML Agents/Grid Sensor", (int)MenuGroup.Sensors)] + public class GridSensorComponent : SensorComponent + { + // dummy sensor only used for debug gizmo + GridSensorBase m_DebugSensor; + List m_Sensors; + internal IGridPerception m_GridPerception; + + /// + /// Name of the generated object. + /// + [HideInInspector, SerializeField] + protected internal string m_SensorName = "GridSensor"; + /// + /// Name of the generated object. + /// Note that changing this at runtime does not affect how the Agent sorts the sensors. + /// + public string SensorName + { + get { return m_SensorName; } + set { m_SensorName = value; } + } + + [HideInInspector, SerializeField] + internal Vector3 m_CellScale = new Vector3(1f, 0.01f, 1f); + + /// + /// The scale of each grid cell. + /// Note that changing this after the sensor is created has no effect. + /// + public Vector3 CellScale + { + get { return m_CellScale; } + set { m_CellScale = value; } + } + + [HideInInspector, SerializeField] + internal Vector3Int m_GridSize = new Vector3Int(16, 1, 16); + /// + /// The number of grid on each side. + /// Note that changing this after the sensor is created has no effect. + /// + public Vector3Int GridSize + { + get { return m_GridSize; } + set + { + if (value.y != 1) + { + m_GridSize = new Vector3Int(value.x, 1, value.z); + } + else + { + m_GridSize = value; + } + } + } + + [HideInInspector, SerializeField] + internal bool m_RotateWithAgent = true; + /// + /// Rotate the grid based on the direction the agent is facing. + /// + public bool RotateWithAgent + { + get { return m_RotateWithAgent; } + set { m_RotateWithAgent = value; } + } + + [HideInInspector, SerializeField] + internal GameObject m_AgentGameObject; + /// + /// The reference of the root of the agent. This is used to disambiguate objects with + /// the same tag as the agent. Defaults to current GameObject. + /// + public GameObject AgentGameObject + { + get { return (m_AgentGameObject == null ? gameObject : m_AgentGameObject); } + set { m_AgentGameObject = value; } + } + + [HideInInspector, SerializeField] + internal string[] m_DetectableTags; + /// + /// List of tags that are detected. + /// Note that changing this after the sensor is created has no effect. + /// + public string[] DetectableTags + { + get { return m_DetectableTags; } + set { m_DetectableTags = value; } + } + + [HideInInspector, SerializeField] + internal LayerMask m_ColliderMask; + /// + /// The layer mask. + /// + public LayerMask ColliderMask + { + get { return m_ColliderMask; } + set { m_ColliderMask = value; } + } + + [HideInInspector, SerializeField] + internal int m_MaxColliderBufferSize = 500; + /// + /// The absolute max size of the Collider buffer used in the non-allocating Physics calls. In other words + /// the Collider buffer will never grow beyond this number even if there are more Colliders in the Grid Cell. + /// Note that changing this after the sensor is created has no effect. + /// + public int MaxColliderBufferSize + { + get { return m_MaxColliderBufferSize; } + set { m_MaxColliderBufferSize = value; } + } + + [HideInInspector, SerializeField] + internal int m_InitialColliderBufferSize = 4; + /// + /// The Estimated Max Number of Colliders to expect per cell. This number is used to + /// pre-allocate an array of Colliders in order to take advantage of the OverlapBoxNonAlloc + /// Physics API. If the number of colliders found is >= InitialColliderBufferSize the array + /// will be resized to double its current size. The hard coded absolute size is 500. + /// Note that changing this after the sensor is created has no effect. + /// + public int InitialColliderBufferSize + { + get { return m_InitialColliderBufferSize; } + set { m_InitialColliderBufferSize = value; } + } + + [HideInInspector, SerializeField] + internal Color[] m_DebugColors; + /// + /// Array of Colors used for the grid gizmos. + /// + public Color[] DebugColors + { + get { return m_DebugColors; } + set { m_DebugColors = value; } + } + + [HideInInspector, SerializeField] + internal float m_GizmoYOffset = 0f; + /// + /// The height of the gizmos grid. + /// + public float GizmoYOffset + { + get { return m_GizmoYOffset; } + set { m_GizmoYOffset = value; } + } + + [HideInInspector, SerializeField] + internal bool m_ShowGizmos = false; + /// + /// Whether to show gizmos or not. + /// + public bool ShowGizmos + { + get { return m_ShowGizmos; } + set { m_ShowGizmos = value; } + } + + [HideInInspector, SerializeField] + internal SensorCompressionType m_CompressionType = SensorCompressionType.PNG; + /// + /// The compression type to use for the sensor. + /// + public SensorCompressionType CompressionType + { + get { return m_CompressionType; } + set { m_CompressionType = value; UpdateSensor(); } + } + + [HideInInspector, SerializeField] + [Range(1, 50)] + [Tooltip("Number of frames of observations that will be stacked before being fed to the neural network.")] + internal int m_ObservationStacks = 1; + /// + /// Whether to stack previous observations. Using 1 means no previous observations. + /// Note that changing this after the sensor is created has no effect. + /// + public int ObservationStacks + { + get { return m_ObservationStacks; } + set { m_ObservationStacks = value; } + } + + /// + public override ISensor[] CreateSensors() + { + m_GridPerception = new BoxOverlapChecker( + m_CellScale, + m_GridSize, + m_RotateWithAgent, + m_ColliderMask, + gameObject, + AgentGameObject, + m_DetectableTags, + m_InitialColliderBufferSize, + m_MaxColliderBufferSize + ); + + // debug data is positive int value and will trigger data validation exception if SensorCompressionType is not None. + m_DebugSensor = new GridSensorBase("DebugGridSensor", m_CellScale, m_GridSize, m_DetectableTags, SensorCompressionType.None); + m_GridPerception.RegisterDebugSensor(m_DebugSensor); + + m_Sensors = GetGridSensors().ToList(); + if (m_Sensors == null || m_Sensors.Count < 1) + { + throw new UnityAgentsException("GridSensorComponent received no sensors. Specify at least one observation type (OneHot/Counting) to use grid sensors." + + "If you're overriding GridSensorComponent.GetGridSensors(), return at least one grid sensor."); + } + + // Only one sensor needs to reference the boxOverlapChecker, so that it gets updated exactly once + m_Sensors[0].m_GridPerception = m_GridPerception; + foreach (var sensor in m_Sensors) + { + m_GridPerception.RegisterSensor(sensor); + } + + if (ObservationStacks != 1) + { + var sensors = new ISensor[m_Sensors.Count]; + for (var i = 0; i < m_Sensors.Count; i++) + { + sensors[i] = new StackingSensor(m_Sensors[i], ObservationStacks); + } + return sensors; + } + else + { + return m_Sensors.ToArray(); + } + } + + /// + /// Get an array of GridSensors to be added in this component. + /// Override this method and return custom GridSensor implementations. + /// + /// Array of grid sensors to be added to the component. + protected virtual GridSensorBase[] GetGridSensors() + { + List sensorList = new List(); + var sensor = new OneHotGridSensor(m_SensorName + "-OneHot", m_CellScale, m_GridSize, m_DetectableTags, m_CompressionType); + sensorList.Add(sensor); + return sensorList.ToArray(); + } + + /// + /// Update fields that are safe to change on the Sensor at runtime. + /// + internal void UpdateSensor() + { + if (m_Sensors != null) + { + m_GridPerception.RotateWithAgent = m_RotateWithAgent; + m_GridPerception.ColliderMask = m_ColliderMask; + foreach (var sensor in m_Sensors) + { + sensor.CompressionType = m_CompressionType; + } + } + } + + void OnDrawGizmos() + { + if (m_ShowGizmos) + { + if (m_GridPerception == null || m_DebugSensor == null) + { + return; + } + + m_DebugSensor.ResetPerceptionBuffer(); + m_GridPerception.UpdateGizmo(); + var cellColors = m_DebugSensor.PerceptionBuffer; + var rotation = m_GridPerception.GetGridRotation(); + + var scale = new Vector3(m_CellScale.x, m_CellScale.y, m_CellScale.z); + var gizmoYOffset = new Vector3(0, m_GizmoYOffset, 0); + var oldGizmoMatrix = Gizmos.matrix; + for (var i = 0; i < m_DebugSensor.PerceptionBuffer.Length; i++) + { + var cellPosition = m_GridPerception.GetCellGlobalPosition(i); + var cubeTransform = Matrix4x4.TRS(cellPosition + gizmoYOffset, rotation, scale); + Gizmos.matrix = oldGizmoMatrix * cubeTransform; + var colorIndex = cellColors[i] - 1; + var debugRayColor = Color.white; + if (colorIndex > -1 && m_DebugColors.Length > colorIndex) + { + debugRayColor = m_DebugColors[(int)colorIndex]; + } + Gizmos.color = new Color(debugRayColor.r, debugRayColor.g, debugRayColor.b, .5f); + Gizmos.DrawCube(Vector3.zero, Vector3.one); + } + + Gizmos.matrix = oldGizmoMatrix; + } + } + } +} diff --git a/com.unity.ml-agents/Runtime/Sensors/GridSensorComponent.cs.meta b/com.unity.ml-agents/Runtime/Sensors/GridSensorComponent.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..8090977a3af2fa7f85b64a2a24904cc2c81ca0eb Binary files /dev/null and b/com.unity.ml-agents/Runtime/Sensors/GridSensorComponent.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Sensors/IBuiltInSensor.cs b/com.unity.ml-agents/Runtime/Sensors/IBuiltInSensor.cs new file mode 100644 index 0000000000000000000000000000000000000000..d69164d8c9b14e3e8a0b574c5fd91cc410b20cab --- /dev/null +++ b/com.unity.ml-agents/Runtime/Sensors/IBuiltInSensor.cs @@ -0,0 +1,69 @@ +namespace Unity.MLAgents.Sensors +{ + /// + /// Identifiers for "built in" sensor types. + /// These are only used for analytics, and should not be used for any runtime decisions. + /// + /// NOTE: Do not renumber these, since the values are used for analytics. Renaming is allowed though. + /// + public enum BuiltInSensorType + { + /// + /// Default Sensor type if it cannot be determined. + /// + Unknown = 0, + /// + /// The Vector sensor used by the agent. + /// + VectorSensor = 1, + /// + /// The Stacking Sensor type. NOTE: StackingSensor actually returns the wrapped sensor's type. + /// + StackingSensor = 2, + /// + /// The RayPerception Sensor types, both 3D and 2D. + /// + RayPerceptionSensor = 3, + /// + /// The observable attribute sensor type. + /// + ReflectionSensor = 4, + /// + /// Sensors that use the Camera for observations. + /// + CameraSensor = 5, + /// + /// Sensors that use RenderTextures for observations. + /// + RenderTextureSensor = 6, + /// + /// Sensors that use buffers or tensors for observations. + /// + BufferSensor = 7, + /// + /// The sensors that observe properties of rigid bodies. + /// + PhysicsBodySensor = 8, + /// + /// The sensors that observe Match 3 boards. + /// + Match3Sensor = 9, + /// + /// Sensors that break down the world into a grid of colliders to observe an area at a pre-defined granularity. + /// + GridSensor = 10 + } + + /// + /// Interface for sensors that are provided as part of ML-Agents. + /// User-implemented sensors don't need to use this interface. + /// + internal interface IBuiltInSensor + { + /// + /// Return the corresponding BuiltInSensorType for the sensor. + /// + /// A BuiltInSensorType corresponding to the sensor. + BuiltInSensorType GetBuiltInSensorType(); + } +} diff --git a/com.unity.ml-agents/Runtime/Sensors/IBuiltInSensor.cs.meta b/com.unity.ml-agents/Runtime/Sensors/IBuiltInSensor.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..93dd08d1f194ab5e65476170704351ccaad7db00 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Sensors/IBuiltInSensor.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Sensors/IGridPerception.cs b/com.unity.ml-agents/Runtime/Sensors/IGridPerception.cs new file mode 100644 index 0000000000000000000000000000000000000000..bbb981efa510cf73848da5bec428c3260263c57d --- /dev/null +++ b/com.unity.ml-agents/Runtime/Sensors/IGridPerception.cs @@ -0,0 +1,62 @@ +using System; +using UnityEngine; + +namespace Unity.MLAgents.Sensors +{ + /// + /// An interface for GridSensor perception that defines the grid cells and collider detecting strategies. + /// + internal interface IGridPerception + { + bool RotateWithAgent + { + get; + set; + } + + LayerMask ColliderMask + { + get; + set; + } + + /// Converts the index of the cell to the 3D point (y is zero) relative to grid center + /// Vector3 of the position of the center of the cell relative to grid center + /// The index of the cell + Vector3 GetCellLocalPosition(int cellIndex); + + /// + /// Converts the index of the cell to the 3D point (y is zero) in world space + /// based on the result from GetCellLocalPosition() + /// + /// Vector3 of the position of the center of the cell in world space + /// The index of the cell + Vector3 GetCellGlobalPosition(int cellIndex); + + Quaternion GetGridRotation(); + + /// + /// Perceive the latest grid status. Detect colliders for each cell, parse the collider arrays, + /// then trigger registered sensors to encode and update with the new grid status. + /// + void Perceive(); + + /// + /// Same as Perceive(), but only load data for debug gizmo. + /// + void UpdateGizmo(); + + /// + /// Register a sensor to this GridPerception to receive the grid perception results. + /// When the GridPerception perceive a new observation, registered sensors will be triggered + /// to encode the new observation and update its data. + /// + void RegisterSensor(GridSensorBase sensor); + + /// + /// Register an internal debug sensor. + /// Debug sensors will only be triggered when drawing debug gizmos. + /// + void RegisterDebugSensor(GridSensorBase debugSensor); + } +} diff --git a/com.unity.ml-agents/Runtime/Sensors/IGridPerception.cs.meta b/com.unity.ml-agents/Runtime/Sensors/IGridPerception.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..b08ed6449d545a290eaa349d4a1e9f3641564f4f Binary files /dev/null and b/com.unity.ml-agents/Runtime/Sensors/IGridPerception.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Sensors/IJointExtractor.cs b/com.unity.ml-agents/Runtime/Sensors/IJointExtractor.cs new file mode 100644 index 0000000000000000000000000000000000000000..c2c0c1fbb081990e58d7d30447a1bf7c62ed8a23 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Sensors/IJointExtractor.cs @@ -0,0 +1,26 @@ +namespace Unity.MLAgents.Sensors +{ + /// + /// Interface for generating observations from a physical joint or constraint. + /// + [UnityEngine.Scripting.APIUpdating.MovedFrom("Unity.MLAgents.Extensions.Sensors")] + public interface IJointExtractor + { + /// + /// Determine the number of observations that would be generated for the particular joint + /// using the provided PhysicsSensorSettings. + /// + /// The settings used to configure the physics sensor. + /// Number of floats that will be written. + int NumObservations(PhysicsSensorSettings settings); + + /// + /// Write the observations to the ObservationWriter, starting at the specified offset. + /// + /// The settings used to configure the physics sensor. + /// The writer to which observations are written. + /// The starting index in the writer to begin writing observations. + /// Number of floats that were written. + int Write(PhysicsSensorSettings settings, ObservationWriter writer, int offset); + } +} diff --git a/com.unity.ml-agents/Runtime/Sensors/IJointExtractor.cs.meta b/com.unity.ml-agents/Runtime/Sensors/IJointExtractor.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..a1ef9c2f7b3f7f04c7567315f35a03aae8347d86 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Sensors/IJointExtractor.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Sensors/ISensor.cs b/com.unity.ml-agents/Runtime/Sensors/ISensor.cs new file mode 100644 index 0000000000000000000000000000000000000000..46786cf64264e0bea5a0531c8986acdd41df98c6 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Sensors/ISensor.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; + +namespace Unity.MLAgents.Sensors +{ + /// + /// The Dimension property flags of the observations + /// + [Flags] + public enum DimensionProperty + { + /// + /// No properties specified. + /// + Unspecified = 0, + + /// + /// No Property of the observation in that dimension. Observation can be processed with + /// fully connected networks. + /// + None = 1, + + /// + /// Means it is suitable to do a convolution in this dimension. + /// + TranslationalEquivariance = 2, + + /// + /// Means that there can be a variable number of observations in this dimension. + /// The observations are unordered. + /// + VariableSize = 4, + } + + /// + /// The ObservationType enum of the Sensor. + /// + public enum ObservationType + { + /// + /// Collected observations are generic. + /// + Default = 0, + + /// + /// Collected observations contain goal information. + /// + GoalSignal = 1, + } + + /// + /// Sensor interface for generating observations. + /// + public interface ISensor + { + /// + /// Returns a description of the observations that will be generated by the sensor. + /// See for more details, and helper methods to create one. + /// + /// An object describing the observation. + ObservationSpec GetObservationSpec(); + + /// + /// Write the observation data directly to the . + /// Note that this (and ) may + /// be called multiple times per agent step, so should not mutate any internal state. + /// + /// Where the observations will be written to. + /// The number of elements written. + int Write(ObservationWriter writer); + + /// + /// Return a compressed representation of the observation. For small observations, + /// this should generally not be implemented. However, compressing large observations + /// (such as visual results) can significantly improve model training time. + /// + /// Compressed observation. + byte[] GetCompressedObservation(); + + /// + /// Update any internal state of the sensor. This is called once per each agent step. + /// + void Update(); + + /// + /// Resets the internal state of the sensor. This is called at the end of an Agent's episode. + /// Most implementations can leave this empty. + /// + void Reset(); + + /// + /// Return information on the compression type being used. If no compression is used, return + /// . + /// + /// An object describing the compression used by the sensor. + CompressionSpec GetCompressionSpec(); + + /// + /// Get the name of the sensor. This is used to ensure deterministic sorting of the sensors + /// on an Agent, so the naming must be consistent across all sensors and agents. + /// + /// The name of the sensor. + string GetName(); + } + + + /// + /// Helper methods to be shared by all classes that implement . + /// + public static class SensorExtensions + { + /// + /// Get the total number of elements in the ISensor's observation (i.e. the product of the + /// shape elements). + /// + /// Sensor + /// The total number of elements in the `ISensor`'s observation. + public static int ObservationSize(this ISensor sensor) + { + var obsSpec = sensor.GetObservationSpec(); + var count = 1; + for (var i = 0; i < obsSpec.Rank; i++) + { + count *= obsSpec.Shape[i]; + } + + return count; + } + } + + internal static class SensorUtils + { + internal static void SortSensors(List sensors) + { + // Use InvariantCulture to ensure consistent sorting between different culture settings. + sensors.Sort((x, y) => string.Compare(x.GetName(), y.GetName(), StringComparison.InvariantCulture)); + } + } +} diff --git a/com.unity.ml-agents/Runtime/Sensors/ISensor.cs.meta b/com.unity.ml-agents/Runtime/Sensors/ISensor.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..d8ceedec70635e68eaa14738f3d174c343e257fd Binary files /dev/null and b/com.unity.ml-agents/Runtime/Sensors/ISensor.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Sensors/ObservationSpec.cs b/com.unity.ml-agents/Runtime/Sensors/ObservationSpec.cs new file mode 100644 index 0000000000000000000000000000000000000000..0e8de15df18d1ed6d821c086d05bbe73022ce219 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Sensors/ObservationSpec.cs @@ -0,0 +1,138 @@ +namespace Unity.MLAgents.Sensors +{ + /// + /// A description of the observations that an ISensor produces. + /// This includes the size of the observation, the properties of each dimension, and how the observation + /// should be used for training. + /// + public struct ObservationSpec + { + internal readonly InplaceArray m_Shape; + + /// + /// The size of the observations that will be generated. + /// For example, a sensor that observes the velocity of a rigid body (in 3D) would use [3]. + /// A sensor that returns an RGB image would use [Height, Width, 3]. + /// + public InplaceArray Shape + { + get => m_Shape; + } + + internal readonly InplaceArray m_DimensionProperties; + + /// + /// The properties of each dimensions of the observation. + /// The length of the array must be equal to the rank of the observation tensor. + /// + /// + /// It is generally recommended to use default values provided by helper functions, + /// as not all combinations of DimensionProperty may be supported by the trainer. + /// + public InplaceArray DimensionProperties + { + get => m_DimensionProperties; + } + + internal ObservationType m_ObservationType; + + /// + /// The type of the observation, e.g. whether they are generic or + /// help determine the goal for the Agent. + /// + public ObservationType ObservationType + { + get => m_ObservationType; + } + + /// + /// The number of dimensions of the observation. + /// + public int Rank + { + get { return Shape.Length; } + } + + /// + /// Construct an ObservationSpec for 1-D observations of the requested length. + /// + /// Length + /// Observation type + /// `ObservationSpec` for 1-D observations of the requested length. + public static ObservationSpec Vector(int length, ObservationType obsType = ObservationType.Default) + { + return new ObservationSpec( + new InplaceArray(length), + new InplaceArray(DimensionProperty.None), + obsType + ); + } + + /// + /// Construct an ObservationSpec for variable-length observations. + /// + /// Observation size + /// Max number of observations + /// `ObservationSpec` for variable-length observations. + public static ObservationSpec VariableLength(int obsSize, int maxNumObs) + { + var dimProps = new InplaceArray( + DimensionProperty.VariableSize, + DimensionProperty.None + ); + return new ObservationSpec( + new InplaceArray(obsSize, maxNumObs), + dimProps + ); + } + + /// + /// Construct an ObservationSpec for visual-like observations, e.g. observations + /// with a height, width, and possible multiple channels. + /// + /// Height + /// Width + /// Channels + /// Observation type + /// `ObservationSpec` for visual-like observations + public static ObservationSpec Visual(int channels, int height, int width, ObservationType obsType = ObservationType.Default) + { + var dimProps = new InplaceArray( + DimensionProperty.None, + DimensionProperty.TranslationalEquivariance, + DimensionProperty.TranslationalEquivariance + ); + return new ObservationSpec( + new InplaceArray(channels, height, width), + dimProps, + obsType + ); + } + + /// + /// Create a general ObservationSpec from the shape, dimension properties, and observation type. + /// + /// + /// Note that not all combinations of DimensionProperty may be supported by the trainer. + /// shape and dimensionProperties must have the same size. + /// + /// Shape + /// Dimension properties + /// Observation type + /// Unity agents exception + public ObservationSpec( + InplaceArray shape, + InplaceArray dimensionProperties, + ObservationType observationType = ObservationType.Default + ) + { + if (shape.Length != dimensionProperties.Length) + { + throw new UnityAgentsException("shape and dimensionProperties must have the same length."); + } + m_Shape = shape; + m_DimensionProperties = dimensionProperties; + m_ObservationType = observationType; + } + } +} diff --git a/com.unity.ml-agents/Runtime/Sensors/ObservationSpec.cs.meta b/com.unity.ml-agents/Runtime/Sensors/ObservationSpec.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..691fdf61727a9cf69a2fb39ce188a012fa844e96 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Sensors/ObservationSpec.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Sensors/ObservationWriter.cs b/com.unity.ml-agents/Runtime/Sensors/ObservationWriter.cs new file mode 100644 index 0000000000000000000000000000000000000000..3095bd75a2996f054dcc3c1559a26fe3650ed725 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Sensors/ObservationWriter.cs @@ -0,0 +1,388 @@ +using System; +using System.Collections.Generic; +using Unity.InferenceEngine; +using Unity.MLAgents.Inference; +using UnityEngine; +using DeviceType = Unity.InferenceEngine.DeviceType; + +namespace Unity.MLAgents.Sensors +{ + /// + /// Allows sensors to write to both TensorProxy and float arrays/lists. + /// + public class ObservationWriter + { + IList m_Data; + int m_Offset; + + TensorProxy m_Proxy; + int m_Batch; + int m_Capacity; + + TensorShape m_TensorShape; + + /// + /// Initializes a new instance of the class. + /// + public ObservationWriter() { } + + /// + /// Set the writer to write to an IList at the given channelOffset. + /// + /// Float array or list that will be written to. + /// ObservationSpec of the observation to be written + /// Offset from the start of the float data to write to. + internal void SetTarget(IList data, ObservationSpec observationSpec, int offset) + { + SetTarget(data, observationSpec.Shape, offset); + } + + /// + /// Set the writer to write to an IList at the given channelOffset. + /// + /// Float array or list that will be written to. + /// Shape of the observations to be written. + /// Offset from the start of the float data to write to. + internal void SetTarget(IList data, InplaceArray shape, int offset) + { + m_Data = data; + m_Offset = offset; + m_Proxy = null; + m_Batch = 0; + + if (shape.Length == 1) + { + m_TensorShape = new TensorShape(m_Batch, shape[0]); + } + else if (shape.Length == 2) + { + m_TensorShape = new TensorShape(new[] { m_Batch, 1, shape[0], shape[1] }); + } + else + { + m_TensorShape = new TensorShape(m_Batch, shape[0], shape[1], shape[2]); + } + } + + /// + /// Set the writer to write to a TensorProxy at the given batch and channel offset. + /// + /// Tensor proxy that will be written to. + /// Batch index in the tensor proxy (i.e. the index of the Agent). + /// Offset from the start of the channel to write to. + internal void SetTarget(TensorProxy tensorProxy, int batchIndex, int channelOffset) + { + m_Proxy = tensorProxy; + m_Batch = batchIndex; + m_Offset = channelOffset; + m_Data = null; + m_TensorShape = m_Proxy.data.shape; + m_Capacity = m_TensorShape.rank >= 2 ? m_TensorShape[1] : 0; + } + + /// + /// 1D write access at a specified index. Use AddList if possible instead. + /// + /// Index to write to. + public float this[int index] + { + set + { + if (m_Data != null) + { + m_Data[index + m_Offset] = value; + } + else + { + if (index + m_Offset < 0 || index + m_Offset >= m_Capacity) + return; + + m_Proxy.data.CompleteAllPendingOperations(); + ((Tensor)m_Proxy.data)[m_Batch, index + m_Offset] = value; + } + } + } + + /// + /// Write access at the specified channel and width. + /// + /// Channels + /// Width + public float this[int ch, int w] + { + set + { + if (m_Data != null) + { + m_Data[ch * m_TensorShape[m_TensorShape.length - 1] + w] = value; + } + else + { + m_Proxy.data.CompleteAllPendingOperations(); + + ((Tensor)m_Proxy.data)[m_Batch, ch, w] = value; + } + } + } + + /// + /// 3D write access at the specified height, width, and channel. + /// + /// Height + /// Width + /// Channels + public float this[int ch, int h, int w] + { + set + { + if (m_Data != null) + { + if (h < 0 || h >= m_TensorShape.Height()) + { + throw new IndexOutOfRangeException($"height value {h} must be in range [0, {m_TensorShape.Height() - 1}]"); + } + + if (w < 0 || w >= m_TensorShape.Width()) + { + throw new IndexOutOfRangeException($"width value {w} must be in range [0, {m_TensorShape.Width() - 1}]"); + } + + if (ch < 0 || ch >= m_TensorShape.Channels()) + { + throw new IndexOutOfRangeException($"channel value {ch} must be in range [0, {m_TensorShape.Channels() - 1}]"); + } + + var index = m_TensorShape.Index(m_Batch, ch + m_Offset, h, w); + m_Data[index] = value; + } + else + { + if (ch + m_Offset < 0 || ch + m_Offset >= m_TensorShape.Channels() || + h < 0 || h >= m_TensorShape.Height() || + w < 0 || w >= m_TensorShape.Width()) + return; + + m_Proxy.data.CompleteAllPendingOperations(); + ((Tensor)m_Proxy.data)[m_Batch, ch + m_Offset, h, w] = value; + } + } + } + + /// + /// Write the list of floats. + /// + /// The actual list of floats to write. + /// Optional write offset to start writing from. + public void AddList(IList data, int writeOffset = 0) + { + if (m_Data != null) + { + for (var index = 0; index < data.Count; index++) + { + var val = data[index]; + m_Data[index + m_Offset + writeOffset] = val; + } + } + else + { + m_Proxy.data.CompleteAllPendingOperations(); + + var maxCount = Math.Min(data.Count, Math.Max(0, m_Capacity - m_Offset - writeOffset)); + for (var index = 0; index < maxCount; index++) + { + ((Tensor)m_Proxy.data)[m_Batch, index + m_Offset + writeOffset] = data[index]; + } + } + } + + /// + /// Write the Vector3 components. + /// + /// The Vector3 to be written. + /// Optional write offset. + public void Add(Vector3 vec, int writeOffset = 0) + { + if (m_Data != null) + { + m_Data[m_Offset + writeOffset + 0] = vec.x; + m_Data[m_Offset + writeOffset + 1] = vec.y; + m_Data[m_Offset + writeOffset + 2] = vec.z; + } + else + { + var start = m_Offset + writeOffset; + var remaining = m_Capacity - start; + if (remaining <= 0) return; + + m_Proxy.data.CompleteAllPendingOperations(); + ((Tensor)m_Proxy.data)[m_Batch, start + 0] = vec.x; + if (remaining <= 1) return; + ((Tensor)m_Proxy.data)[m_Batch, start + 1] = vec.y; + if (remaining <= 2) return; + ((Tensor)m_Proxy.data)[m_Batch, start + 2] = vec.z; + } + } + + /// + /// Write the Vector4 components. + /// + /// The Vector4 to be written. + /// Optional write offset. + public void Add(Vector4 vec, int writeOffset = 0) + { + if (m_Data != null) + { + m_Data[m_Offset + writeOffset + 0] = vec.x; + m_Data[m_Offset + writeOffset + 1] = vec.y; + m_Data[m_Offset + writeOffset + 2] = vec.z; + m_Data[m_Offset + writeOffset + 3] = vec.w; + } + else + { + var start = m_Offset + writeOffset; + var remaining = m_Capacity - start; + if (remaining <= 0) return; + + m_Proxy.data.CompleteAllPendingOperations(); + ((Tensor)m_Proxy.data)[m_Batch, start + 0] = vec.x; + if (remaining <= 1) return; + ((Tensor)m_Proxy.data)[m_Batch, start + 1] = vec.y; + if (remaining <= 2) return; + ((Tensor)m_Proxy.data)[m_Batch, start + 2] = vec.z; + if (remaining <= 3) return; + ((Tensor)m_Proxy.data)[m_Batch, start + 3] = vec.w; + } + } + + /// + /// Write the Quaternion components. + /// + /// The Quaternion to be written. + /// Optional write offset. + public void Add(Quaternion quat, int writeOffset = 0) + { + if (m_Data != null) + { + m_Data[m_Offset + writeOffset + 0] = quat.x; + m_Data[m_Offset + writeOffset + 1] = quat.y; + m_Data[m_Offset + writeOffset + 2] = quat.z; + m_Data[m_Offset + writeOffset + 3] = quat.w; + } + else + { + var start = m_Offset + writeOffset; + var remaining = m_Capacity - start; + if (remaining <= 0) return; + + m_Proxy.data.CompleteAllPendingOperations(); + ((Tensor)m_Proxy.data)[m_Batch, start + 0] = quat.x; + if (remaining <= 1) return; + ((Tensor)m_Proxy.data)[m_Batch, start + 1] = quat.y; + if (remaining <= 2) return; + ((Tensor)m_Proxy.data)[m_Batch, start + 2] = quat.z; + if (remaining <= 3) return; + ((Tensor)m_Proxy.data)[m_Batch, start + 3] = quat.w; + } + } + } + + /// + /// Provides extension methods for the ObservationWriter. + /// + public static class ObservationWriterExtension + { + /// + /// Writes a Texture2D into a ObservationWriter. + /// + /// + /// Writer to fill with Texture data. + /// + /// + /// The texture to be put into the tensor. + /// + /// + /// If set to true the textures will be converted to grayscale before + /// being stored in the tensor. + /// + /// The number of floats written + public static int WriteTexture( + this ObservationWriter obsWriter, + Texture2D texture, + bool grayScale) + { + if (texture.format == TextureFormat.RGB24) + { + return obsWriter.WriteTextureRGB24(texture, grayScale); + } + + var width = texture.width; + var height = texture.height; + + var texturePixels = texture.GetPixels32(); + + // During training, we convert from Texture to PNG before sending to the trainer, which has the + // effect of flipping the image. We need another flip here at inference time to match this. + for (var h = height - 1; h >= 0; h--) + { + for (var w = 0; w < width; w++) + { + var currentPixel = texturePixels[(height - h - 1) * width + w]; + + if (grayScale) + { + obsWriter[0, h, w] = + (currentPixel.r + currentPixel.g + currentPixel.b) / 3f / 255.0f; + } + else + { + // For Color32, the r, g and b values are between 0 and 255. + obsWriter[0, h, w] = currentPixel.r / 255.0f; + obsWriter[1, h, w] = currentPixel.g / 255.0f; + obsWriter[2, h, w] = currentPixel.b / 255.0f; + } + } + } + + return height * width * (grayScale ? 1 : 3); + } + + internal static int WriteTextureRGB24( + this ObservationWriter obsWriter, + Texture2D texture, + bool grayScale + ) + { + var width = texture.width; + var height = texture.height; + + var rawBytes = texture.GetRawTextureData(); + + // During training, we convert from Texture to PNG before sending to the trainer, which has the + // effect of flipping the image. We need another flip here at inference time to match this. + for (var h = height - 1; h >= 0; h--) + { + for (var w = 0; w < width; w++) + { + var offset = (height - h - 1) * width + w; + var r = rawBytes[3 * offset]; + var g = rawBytes[3 * offset + 1]; + var b = rawBytes[3 * offset + 2]; + + if (grayScale) + { + obsWriter[0, h, w] = (r + g + b) / 3f / 255.0f; + } + else + { + // For Color32, the r, g and b values are between 0 and 255. + obsWriter[0, h, w] = r / 255.0f; + obsWriter[1, h, w] = g / 255.0f; + obsWriter[2, h, w] = b / 255.0f; + } + } + } + + return height * width * (grayScale ? 1 : 3); + } + } +} diff --git a/com.unity.ml-agents/Runtime/Sensors/ObservationWriter.cs.meta b/com.unity.ml-agents/Runtime/Sensors/ObservationWriter.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..62fc3b1aba72ec255268fe32d5a149e513686c1e Binary files /dev/null and b/com.unity.ml-agents/Runtime/Sensors/ObservationWriter.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Sensors/OneHotGridSensor.cs b/com.unity.ml-agents/Runtime/Sensors/OneHotGridSensor.cs new file mode 100644 index 0000000000000000000000000000000000000000..89be650e05f4ccfa13045b376a4778f228e4c8ad --- /dev/null +++ b/com.unity.ml-agents/Runtime/Sensors/OneHotGridSensor.cs @@ -0,0 +1,59 @@ +using UnityEngine; + +namespace Unity.MLAgents.Sensors +{ + /// + /// Grid-based sensor with one-hot observations. + /// + public class OneHotGridSensor : GridSensorBase + { + /// + /// Create a OneHotGridSensor with the specified configuration. + /// + /// The sensor name + /// The scale of each cell in the grid + /// Number of cells on each side of the grid + /// Tags to be detected by the sensor + /// Compression type + public OneHotGridSensor( + string name, + Vector3 cellScale, + Vector3Int gridSize, + string[] detectableTags, + SensorCompressionType compression + ) : base(name, cellScale, gridSize, detectableTags, compression) + { + } + + /// + protected override int GetCellObservationSize() + { + return DetectableTags == null ? 0 : DetectableTags.Length; + } + + /// + protected override bool IsDataNormalized() + { + return true; + } + + /// + protected internal override ProcessCollidersMethod GetProcessCollidersMethod() + { + return ProcessCollidersMethod.ProcessClosestColliders; + } + + /// + /// Get the one-hot representation of the detected game object's tag. + /// + /// The game object that was detected within a certain cell + /// The index of the detectedObject's tag in the DetectableObjects list + /// The buffer to write the observation values. + /// The buffer size is configured by . + /// + protected override void GetObjectData(GameObject detectedObject, int tagIndex, float[] dataBuffer) + { + dataBuffer[tagIndex] = 1; + } + } +} diff --git a/com.unity.ml-agents/Runtime/Sensors/OneHotGridSensor.cs.meta b/com.unity.ml-agents/Runtime/Sensors/OneHotGridSensor.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..c21f87cba487491aaa0baff45cd475ce97ecd315 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Sensors/OneHotGridSensor.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Sensors/PhysicsBodySensor.cs b/com.unity.ml-agents/Runtime/Sensors/PhysicsBodySensor.cs new file mode 100644 index 0000000000000000000000000000000000000000..b859d078b2dd8a3b72a3c718d358c916fd01cd80 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Sensors/PhysicsBodySensor.cs @@ -0,0 +1,131 @@ +using System.Collections.Generic; +#if UNITY_2020_1_OR_NEWER +using UnityEngine; +#endif + +namespace Unity.MLAgents.Sensors +{ + /// + /// ISensor implementation that generates observations for a group of Rigidbodies or ArticulationBodies. + /// + [UnityEngine.Scripting.APIUpdating.MovedFrom("Unity.MLAgents.Extensions.Sensors")] + public class PhysicsBodySensor : ISensor, IBuiltInSensor + { + ObservationSpec m_ObservationSpec; + string m_SensorName; + + PoseExtractor m_PoseExtractor; + List m_JointExtractors; + PhysicsSensorSettings m_Settings; + + /// + /// Construct a new PhysicsBodySensor + /// + /// The pose extractor used to obtain rigid body poses. + /// The settings used to configure the physics sensor. + /// The name assigned to the sensor. + public PhysicsBodySensor( + RigidBodyPoseExtractor poseExtractor, + PhysicsSensorSettings settings, + string sensorName + ) + { + m_PoseExtractor = poseExtractor; + m_SensorName = sensorName; + m_Settings = settings; + + var numJointExtractorObservations = 0; + m_JointExtractors = new List(poseExtractor.NumEnabledPoses); + foreach (var rb in poseExtractor.GetEnabledRigidbodies()) + { + var jointExtractor = new RigidBodyJointExtractor(rb); + numJointExtractorObservations += jointExtractor.NumObservations(settings); + m_JointExtractors.Add(jointExtractor); + } + + var numTransformObservations = m_PoseExtractor.GetNumPoseObservations(settings); + m_ObservationSpec = ObservationSpec.Vector(numTransformObservations + numJointExtractorObservations); + } + +#if UNITY_2020_1_OR_NEWER + public PhysicsBodySensor(ArticulationBody rootBody, PhysicsSensorSettings settings, string sensorName = null) + { + var poseExtractor = new ArticulationBodyPoseExtractor(rootBody); + m_PoseExtractor = poseExtractor; + m_SensorName = string.IsNullOrEmpty(sensorName) ? $"ArticulationBodySensor:{rootBody?.name}" : sensorName; + m_Settings = settings; + + var numJointExtractorObservations = 0; + m_JointExtractors = new List(poseExtractor.NumEnabledPoses); + foreach (var articBody in poseExtractor.GetEnabledArticulationBodies()) + { + var jointExtractor = new ArticulationBodyJointExtractor(articBody); + numJointExtractorObservations += jointExtractor.NumObservations(settings); + m_JointExtractors.Add(jointExtractor); + } + + var numTransformObservations = m_PoseExtractor.GetNumPoseObservations(settings); + m_ObservationSpec = ObservationSpec.Vector(numTransformObservations + numJointExtractorObservations); + } + +#endif + + /// + public ObservationSpec GetObservationSpec() + { + return m_ObservationSpec; + } + + /// + public int Write(ObservationWriter writer) + { + var numWritten = writer.WritePoses(m_Settings, m_PoseExtractor); + foreach (var jointExtractor in m_JointExtractors) + { + numWritten += jointExtractor.Write(m_Settings, writer, numWritten); + } + return numWritten; + } + + /// + public byte[] GetCompressedObservation() + { + return null; + } + + /// + public void Update() + { + if (m_Settings.UseModelSpace) + { + m_PoseExtractor.UpdateModelSpacePoses(); + } + + if (m_Settings.UseLocalSpace) + { + m_PoseExtractor.UpdateLocalSpacePoses(); + } + } + + /// + public void Reset() { } + + /// + public CompressionSpec GetCompressionSpec() + { + return CompressionSpec.Default(); + } + + /// + public string GetName() + { + return m_SensorName; + } + + /// + public BuiltInSensorType GetBuiltInSensorType() + { + return BuiltInSensorType.PhysicsBodySensor; + } + } +} diff --git a/com.unity.ml-agents/Runtime/Sensors/PhysicsBodySensor.cs.meta b/com.unity.ml-agents/Runtime/Sensors/PhysicsBodySensor.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..2fce9c0200134a6e5980c92237a86b3a9af4e7f3 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Sensors/PhysicsBodySensor.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Sensors/PhysicsSensorSettings.cs b/com.unity.ml-agents/Runtime/Sensors/PhysicsSensorSettings.cs new file mode 100644 index 0000000000000000000000000000000000000000..e503f9e988828468d87073513c3d618b1d9eea33 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Sensors/PhysicsSensorSettings.cs @@ -0,0 +1,152 @@ +using System; + +namespace Unity.MLAgents.Sensors +{ + /// + /// Settings that define the observations generated for physics-based sensors. + /// + [UnityEngine.Scripting.APIUpdating.MovedFrom("Unity.MLAgents.Extensions.Sensors")] + [Serializable] + public struct PhysicsSensorSettings + { + /// + /// Whether to use model space (relative to the root body) translations as observations. + /// + public bool UseModelSpaceTranslations; + + /// + /// Whether to use model space (relative to the root body) rotations as observations. + /// + public bool UseModelSpaceRotations; + + /// + /// Whether to use local space (relative to the parent body) translations as observations. + /// + public bool UseLocalSpaceTranslations; + + /// + /// Whether to use local space (relative to the parent body) translations as observations. + /// + public bool UseLocalSpaceRotations; + + /// + /// Whether to use model space (relative to the root body) linear velocities as observations. + /// + public bool UseModelSpaceLinearVelocity; + + /// + /// Whether to use local space (relative to the parent body) linear velocities as observations. + /// + public bool UseLocalSpaceLinearVelocity; + + /// + /// Whether to use joint-specific positions and angles as observations. + /// + public bool UseJointPositionsAndAngles; + + /// + /// Whether to use the joint forces and torques that are applied by the solver as observations. + /// + public bool UseJointForces; + + /// + /// Creates a PhysicsSensorSettings with reasonable default values. + /// + /// `PhysicsSensorSettings` with reasonable default values. + public static PhysicsSensorSettings Default() + { + return new PhysicsSensorSettings + { + UseModelSpaceTranslations = true, + UseModelSpaceRotations = true, + }; + } + + /// + /// Whether any model space observations are being used. + /// + public bool UseModelSpace + { + get { return UseModelSpaceTranslations || UseModelSpaceRotations || UseModelSpaceLinearVelocity; } + } + + /// + /// Whether any local space observations are being used. + /// + public bool UseLocalSpace + { + get { return UseLocalSpaceTranslations || UseLocalSpaceRotations || UseLocalSpaceLinearVelocity; } + } + } + + internal static class ObservationWriterPhysicsExtensions + { + /// + /// Utility method for writing a PoseExtractor to an ObservationWriter. + /// + /// + /// + /// + /// The offset into the ObservationWriter to start writing at. + /// The number of observations written. + public static int WritePoses(this ObservationWriter writer, PhysicsSensorSettings settings, PoseExtractor poseExtractor, int baseOffset = 0) + { + var offset = baseOffset; + if (settings.UseModelSpace) + { + foreach (var pose in poseExtractor.GetEnabledModelSpacePoses()) + { + if (settings.UseModelSpaceTranslations) + { + writer.Add(pose.position, offset); + offset += 3; + } + + if (settings.UseModelSpaceRotations) + { + writer.Add(pose.rotation, offset); + offset += 4; + } + } + + foreach (var vel in poseExtractor.GetEnabledModelSpaceVelocities()) + { + if (settings.UseModelSpaceLinearVelocity) + { + writer.Add(vel, offset); + offset += 3; + } + } + } + + if (settings.UseLocalSpace) + { + foreach (var pose in poseExtractor.GetEnabledLocalSpacePoses()) + { + if (settings.UseLocalSpaceTranslations) + { + writer.Add(pose.position, offset); + offset += 3; + } + + if (settings.UseLocalSpaceRotations) + { + writer.Add(pose.rotation, offset); + offset += 4; + } + } + + foreach (var vel in poseExtractor.GetEnabledLocalSpaceVelocities()) + { + if (settings.UseLocalSpaceLinearVelocity) + { + writer.Add(vel, offset); + offset += 3; + } + } + } + + return offset - baseOffset; + } + } +} diff --git a/com.unity.ml-agents/Runtime/Sensors/PhysicsSensorSettings.cs.meta b/com.unity.ml-agents/Runtime/Sensors/PhysicsSensorSettings.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..04f26d1a8a06b8e86d9ef80fec16ac5d12c9b5f2 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Sensors/PhysicsSensorSettings.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Sensors/PoseExtractor.cs b/com.unity.ml-agents/Runtime/Sensors/PoseExtractor.cs new file mode 100644 index 0000000000000000000000000000000000000000..b20adeaa78781fd7af1ac77be5752095c35625e8 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Sensors/PoseExtractor.cs @@ -0,0 +1,481 @@ +using System; +using System.Collections.Generic; +using UnityEngine; +using Object = UnityEngine.Object; + +namespace Unity.MLAgents.Sensors +{ + /// + /// Abstract class for managing the transforms of a hierarchy of objects. + /// This could be GameObjects or Monobehaviours in the scene graph, but this is + /// not a requirement; for example, the objects could be rigid bodies whose hierarchy + /// is defined by Joint configurations. + /// + /// Poses are either considered in model space, which is relative to a root body, + /// or in local space, which is relative to their parent. + /// + [UnityEngine.Scripting.APIUpdating.MovedFrom("Unity.MLAgents.Extensions.Sensors")] + public abstract class PoseExtractor + { + int[] m_ParentIndices; + Pose[] m_ModelSpacePoses; + Pose[] m_LocalSpacePoses; + + Vector3[] m_ModelSpaceLinearVelocities; + Vector3[] m_LocalSpaceLinearVelocities; + + bool[] m_PoseEnabled; + + + /// + /// Read iterator for the enabled model space transforms. + /// + /// An enumerable of enabled model space poses. + public IEnumerable GetEnabledModelSpacePoses() + { + if (m_ModelSpacePoses == null) + { + yield break; + } + + for (var i = 0; i < m_ModelSpacePoses.Length; i++) + { + if (m_PoseEnabled[i]) + { + yield return m_ModelSpacePoses[i]; + } + } + } + + /// + /// Read iterator for the enabled local space transforms. + /// + /// An enumerable of enabled local space poses. + public IEnumerable GetEnabledLocalSpacePoses() + { + if (m_LocalSpacePoses == null) + { + yield break; + } + + for (var i = 0; i < m_LocalSpacePoses.Length; i++) + { + if (m_PoseEnabled[i]) + { + yield return m_LocalSpacePoses[i]; + } + } + } + + /// + /// Read iterator for the enabled model space linear velocities. + /// + /// An enumerable of enabled model space linear velocities. + public IEnumerable GetEnabledModelSpaceVelocities() + { + if (m_ModelSpaceLinearVelocities == null) + { + yield break; + } + + for (var i = 0; i < m_ModelSpaceLinearVelocities.Length; i++) + { + if (m_PoseEnabled[i]) + { + yield return m_ModelSpaceLinearVelocities[i]; + } + } + } + + /// + /// Read iterator for the enabled local space linear velocities. + /// + /// An enumerable of enabled local space linear velocities. + public IEnumerable GetEnabledLocalSpaceVelocities() + { + if (m_LocalSpaceLinearVelocities == null) + { + yield break; + } + + for (var i = 0; i < m_LocalSpaceLinearVelocities.Length; i++) + { + if (m_PoseEnabled[i]) + { + yield return m_LocalSpaceLinearVelocities[i]; + } + } + } + + /// + /// Number of enabled poses in the hierarchy (read-only). + /// + public int NumEnabledPoses + { + get + { + if (m_PoseEnabled == null) + { + return 0; + } + + var numEnabled = 0; + for (var i = 0; i < m_PoseEnabled.Length; i++) + { + numEnabled += m_PoseEnabled[i] ? 1 : 0; + } + + return numEnabled; + } + } + + /// + /// Number of total poses in the hierarchy (read-only). + /// + public int NumPoses + { + get { return m_ModelSpacePoses?.Length ?? 0; } + } + + /// + /// Get the parent index of the body at the specified index. + /// + /// The index of the body whose parent index is to be retrieved. + /// The parent index of the body at the specified index. + public int GetParentIndex(int index) + { + if (m_ParentIndices == null) + { + throw new NullReferenceException("No parent indices set"); + } + + return m_ParentIndices[index]; + } + + /// + /// Set whether the pose at the given index is enabled or disabled for observations. + /// + /// The index of the pose to enable or disable. + /// Whether the pose is enabled (true) or disabled (false). + public void SetPoseEnabled(int index, bool val) + { + m_PoseEnabled[index] = val; + } + + /// + /// Returns whether the pose at the given index is enabled for observations. + /// + /// The index of the pose to check. + /// True if the pose is enabled; otherwise, false. + public bool IsPoseEnabled(int index) + { + return m_PoseEnabled[index]; + } + + /// + /// Initialize with the mapping of parent indices. + /// The 0th element is assumed to be -1, indicating that it's the root. + /// + /// An array mapping each pose to its parent index. The root should be -1. + protected void Setup(int[] parentIndices) + { +#if DEBUG + if (parentIndices[0] != -1) + { + throw new UnityAgentsException($"Expected parentIndices[0] to be -1, got {parentIndices[0]}"); + } +#endif + m_ParentIndices = parentIndices; + var numPoses = parentIndices.Length; + m_ModelSpacePoses = new Pose[numPoses]; + m_LocalSpacePoses = new Pose[numPoses]; + + m_ModelSpaceLinearVelocities = new Vector3[numPoses]; + m_LocalSpaceLinearVelocities = new Vector3[numPoses]; + + m_PoseEnabled = new bool[numPoses]; + // All poses are enabled by default. Generally we'll want to disable the root though. + for (var i = 0; i < numPoses; i++) + { + m_PoseEnabled[i] = true; + } + } + + /// + /// Return the world space Pose of the i'th object. + /// + /// The index of the pose to retrieve. + /// The world space Pose at given index. + protected internal abstract Pose GetPoseAt(int index); + + /// + /// Return the world space linear velocity of the i'th object. + /// + /// The index of the pose for which to get the linear velocity. + /// The world space linear velocity at given index. + protected internal abstract Vector3 GetLinearVelocityAt(int index); + + /// + /// Return the underlying object at the given index. This is only + /// used for display in the inspector. + /// + /// The index of the object to retrieve. + /// The `Object` at given index. + protected internal virtual Object GetObjectAt(int index) + { + return null; + } + + /// + /// Update the internal model space transform storage based on the underlying system. + /// + public void UpdateModelSpacePoses() + { + using (TimerStack.Instance.Scoped("UpdateModelSpacePoses")) + { + if (m_ModelSpacePoses == null) + { + return; + } + + var rootWorldTransform = GetPoseAt(0); + var worldToModel = rootWorldTransform.Inverse(); + var rootLinearVel = GetLinearVelocityAt(0); + + for (var i = 0; i < m_ModelSpacePoses.Length; i++) + { + var currentWorldSpacePose = GetPoseAt(i); + var currentModelSpacePose = worldToModel.Multiply(currentWorldSpacePose); + m_ModelSpacePoses[i] = currentModelSpacePose; + + var currentBodyLinearVel = GetLinearVelocityAt(i); + var relativeVelocity = currentBodyLinearVel - rootLinearVel; + m_ModelSpaceLinearVelocities[i] = worldToModel.rotation * relativeVelocity; + } + } + } + + /// + /// Update the internal model space transform storage based on the underlying system. + /// + public void UpdateLocalSpacePoses() + { + using (TimerStack.Instance.Scoped("UpdateLocalSpacePoses")) + { + if (m_LocalSpacePoses == null) + { + return; + } + + for (var i = 0; i < m_LocalSpacePoses.Length; i++) + { + if (m_ParentIndices[i] != -1) + { + var parentTransform = GetPoseAt(m_ParentIndices[i]); + // This is slightly inefficient, since for a body with multiple children, we'll end up inverting + // the transform multiple times. Might be able to trade space for perf here. + var invParent = parentTransform.Inverse(); + var currentTransform = GetPoseAt(i); + m_LocalSpacePoses[i] = invParent.Multiply(currentTransform); + + var parentLinearVel = GetLinearVelocityAt(m_ParentIndices[i]); + var currentLinearVel = GetLinearVelocityAt(i); + m_LocalSpaceLinearVelocities[i] = invParent.rotation * (currentLinearVel - parentLinearVel); + } + else + { + m_LocalSpacePoses[i] = Pose.identity; + m_LocalSpaceLinearVelocities[i] = Vector3.zero; + } + } + } + } + + /// + /// Compute the number of floats needed to represent the poses for the given PhysicsSensorSettings. + /// + /// The settings used to configure the physics sensor. + /// The number of floats needed to represent the poses for the given `PhysicsSensorSettings`. + public int GetNumPoseObservations(PhysicsSensorSettings settings) + { + int obsPerPose = 0; + obsPerPose += settings.UseModelSpaceTranslations ? 3 : 0; + obsPerPose += settings.UseModelSpaceRotations ? 4 : 0; + obsPerPose += settings.UseLocalSpaceTranslations ? 3 : 0; + obsPerPose += settings.UseLocalSpaceRotations ? 4 : 0; + + obsPerPose += settings.UseModelSpaceLinearVelocity ? 3 : 0; + obsPerPose += settings.UseLocalSpaceLinearVelocity ? 3 : 0; + + return NumEnabledPoses * obsPerPose; + } + + internal void DrawModelSpace(Vector3 offset) + { + UpdateLocalSpacePoses(); + UpdateModelSpacePoses(); + + var pose = m_ModelSpacePoses; + var localPose = m_LocalSpacePoses; + for (var i = 0; i < pose.Length; i++) + { + var current = pose[i]; + if (m_ParentIndices[i] == -1) + { + continue; + } + + var parent = pose[m_ParentIndices[i]]; + Debug.DrawLine(current.position + offset, parent.position + offset, Color.cyan); + var localUp = localPose[i].rotation * Vector3.up; + var localFwd = localPose[i].rotation * Vector3.forward; + var localRight = localPose[i].rotation * Vector3.right; + Debug.DrawLine(current.position + offset, current.position + offset + .1f * localUp, Color.red); + Debug.DrawLine(current.position + offset, current.position + offset + .1f * localFwd, Color.green); + Debug.DrawLine(current.position + offset, current.position + offset + .1f * localRight, Color.blue); + } + } + + /// + /// Simplified representation of the a node in the hierarchy for display. + /// + internal struct DisplayNode + { + /// + /// Underlying object in the hierarchy. Pass to EditorGUIUtility.ObjectContent() for display. + /// + public Object NodeObject; + + /// + /// Whether the poses for the object are enabled. + /// + public bool Enabled; + + /// + /// Depth in the hierarchy, used for adjusting the indent level. + /// + public int Depth; + + /// + /// The index of the corresponding object in the PoseExtractor. + /// + public int OriginalIndex; + } + + /// + /// Get a list of display nodes in depth-first order. + /// + /// The display nodes. + internal IList GetDisplayNodes() + { + if (NumPoses == 0) + { + return Array.Empty(); + } + var nodesOut = new List(NumPoses); + + // List of children for each node + var tree = new Dictionary>(); + for (var i = 0; i < NumPoses; i++) + { + var parent = GetParentIndex(i); + if (i == -1) + { + continue; + } + + if (!tree.ContainsKey(parent)) + { + tree[parent] = new List(); + } + tree[parent].Add(i); + } + + // Store (index, depth) in the stack + var stack = new Stack<(int, int)>(); + stack.Push((0, 0)); + + while (stack.Count != 0) + { + var (current, depth) = stack.Pop(); + var obj = GetObjectAt(current); + + var node = new DisplayNode + { + NodeObject = obj, + Enabled = IsPoseEnabled(current), + OriginalIndex = current, + Depth = depth + }; + nodesOut.Add(node); + + // Add children + if (tree.ContainsKey(current)) + { + // Push to the stack in reverse order + var children = tree[current]; + for (var childIdx = children.Count - 1; childIdx >= 0; childIdx--) + { + stack.Push((children[childIdx], depth + 1)); + } + } + + // Safety check + // This shouldn't even happen, but in case we have a cycle in the graph + // exit instead of looping forever and eating up all the memory. + if (nodesOut.Count > NumPoses) + { + return nodesOut; + } + } + + return nodesOut; + } + } + + /// + /// Extension methods for the Pose struct, in order to improve the readability of some math. + /// + public static class PoseExtensions + { + /// + /// Compute the inverse of a Pose. For any Pose P, + /// P.Inverse() * P + /// will equal the identity pose (within tolerance). + /// + /// The pose to operate on. + /// Inverse `Pose`. + public static Pose Inverse(this Pose pose) + { + var rotationInverse = Quaternion.Inverse(pose.rotation); + var translationInverse = -(rotationInverse * pose.position); + return new Pose { rotation = rotationInverse, position = translationInverse }; + } + + /// + /// This is equivalent to Pose.GetTransformedBy(), but keeps the order more intuitive. + /// + /// The pose to transform by. + /// The pose to be transformed. + /// Multiplied `Pose`. + public static Pose Multiply(this Pose pose, Pose rhs) + { + return rhs.GetTransformedBy(pose); + } + + /// + /// Transform the vector by the pose. Conceptually this is equivalent to treating the Pose + /// as a 4x4 matrix and multiplying the augmented vector. + /// See https://en.wikipedia.org/wiki/Affine_transformation#Augmented_matrix for more details. + /// + /// The pose to transform by. + /// The vector to be transformed. + /// Multiplied `Pose`. + public static Vector3 Multiply(this Pose pose, Vector3 rhs) + { + return pose.rotation * rhs + pose.position; + } + + // TODO optimize inv(A)*B? + } +} diff --git a/com.unity.ml-agents/Runtime/Sensors/PoseExtractor.cs.meta b/com.unity.ml-agents/Runtime/Sensors/PoseExtractor.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..5a16a709bef3f1384cd6d848e1873b123007a99c Binary files /dev/null and b/com.unity.ml-agents/Runtime/Sensors/PoseExtractor.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Sensors/RayPerceptionSensor.cs b/com.unity.ml-agents/Runtime/Sensors/RayPerceptionSensor.cs new file mode 100644 index 0000000000000000000000000000000000000000..332b40ad5655a319510afd35bd7188b398e82f50 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Sensors/RayPerceptionSensor.cs @@ -0,0 +1,670 @@ +using System; +using System.Collections.Generic; +using Unity.Collections; +using Unity.Jobs; +using UnityEngine; + +namespace Unity.MLAgents.Sensors +{ + /// + /// Determines which dimensions the sensor will perform the casts in. + /// + public enum RayPerceptionCastType + { + /// + /// Cast in 2 dimensions, using Physics2D.CircleCast or Physics2D.RayCast. + /// + Cast2D, + + /// + /// Cast in 3 dimensions, using Physics.SphereCast or Physics.RayCast. + /// + Cast3D, + } + + /// + /// Contains the elements that define a ray perception sensor. + /// + public struct RayPerceptionInput + { + /// + /// Length of the rays to cast. This will be scaled up or down based on the scale of the transform. + /// + public float RayLength; + + /// + /// List of tags which correspond to object types agent can see. + /// + public IReadOnlyList DetectableTags; + + /// + /// List of angles (in degrees) used to define the rays. + /// 90 degrees is considered "forward" relative to the game object. + /// + public IReadOnlyList Angles; + + /// + /// Starting height offset of ray from center of agent + /// + public float StartOffset; + + /// + /// Ending height offset of ray from center of agent. + /// + public float EndOffset; + + /// + /// Radius of the sphere to use for spherecasting. + /// If 0 or less, rays are used instead - this may be faster, especially for complex environments. + /// + public float CastRadius; + + /// + /// Transform of the GameObject. + /// + public Transform Transform; + + /// + /// Whether to perform the casts in 2D or 3D. + /// + public RayPerceptionCastType CastType; + + /// + /// Filtering options for the casts. + /// + public int LayerMask; + + /// + /// Whether to use batched raycasts. + /// + public bool UseBatchedRaycasts; + + /// + /// Returns the expected number of floats in the output. + /// + /// The expected number of floats in the output. + public int OutputSize() + { + return ((DetectableTags?.Count ?? 0) + 2) * (Angles?.Count ?? 0); + } + + /// + /// Get the cast start and end points for the given ray index/ + /// + /// Ray index + /// A tuple of the start and end positions in world space. + public (Vector3 StartPositionWorld, Vector3 EndPositionWorld) RayExtents(int rayIndex) + { + var angle = Angles[rayIndex]; + Vector3 startPositionLocal, endPositionLocal; + if (CastType == RayPerceptionCastType.Cast3D) + { + startPositionLocal = new Vector3(0, StartOffset, 0); + endPositionLocal = PolarToCartesian3D(RayLength, angle); + endPositionLocal.y += EndOffset; + } + else + { + // Vector2s here get converted to Vector3s (and back to Vector2s for casting) + startPositionLocal = new Vector2(); + endPositionLocal = PolarToCartesian2D(RayLength, angle); + } + + var startPositionWorld = Transform.TransformPoint(startPositionLocal); + var endPositionWorld = Transform.TransformPoint(endPositionLocal); + + return (StartPositionWorld: startPositionWorld, EndPositionWorld: endPositionWorld); + } + + /// + /// Converts polar coordinate to cartesian coordinate. + /// + static internal Vector3 PolarToCartesian3D(float radius, float angleDegrees) + { + var x = radius * Mathf.Cos(Mathf.Deg2Rad * angleDegrees); + var z = radius * Mathf.Sin(Mathf.Deg2Rad * angleDegrees); + return new Vector3(x, 0f, z); + } + + /// + /// Converts polar coordinate to cartesian coordinate. + /// + static internal Vector2 PolarToCartesian2D(float radius, float angleDegrees) + { + var x = radius * Mathf.Cos(Mathf.Deg2Rad * angleDegrees); + var y = radius * Mathf.Sin(Mathf.Deg2Rad * angleDegrees); + return new Vector2(x, y); + } + } + + /// + /// Contains the data generated/produced from a ray perception sensor. + /// + public class RayPerceptionOutput + { + /// + /// Contains the data generated from a single ray of a ray perception sensor. + /// + public struct RayOutput + { + /// + /// Whether or not the ray hit anything. + /// + public bool HasHit; + + /// + /// Whether or not the ray hit an object whose tag is in the input's DetectableTags list. + /// + public bool HitTaggedObject; + + /// + /// The index of the hit object's tag in the DetectableTags list, or -1 if there was no hit, or the + /// hit object has a different tag. + /// + public int HitTagIndex; + + /// + /// Normalized distance to the hit object. + /// + public float HitFraction; + + /// + /// The hit GameObject (or null if there was no hit). + /// + public GameObject HitGameObject; + + /// + /// Start position of the ray in world space. + /// + public Vector3 StartPositionWorld; + + /// + /// End position of the ray in world space. + /// + public Vector3 EndPositionWorld; + + /// + /// The scaled length of the ray. + /// + /// + /// If there is non-(1,1,1) scale, |EndPositionWorld - StartPositionWorld| will be different from + /// the input rayLength. + /// + public float ScaledRayLength + { + get + { + var rayDirection = EndPositionWorld - StartPositionWorld; + return rayDirection.magnitude; + } + } + + /// + /// The scaled size of the cast. + /// + /// + /// If there is non-(1,1,1) scale, the cast radius will be also be scaled. + /// + public float ScaledCastRadius; + + /// + /// Writes the ray output information to a subset of the float array. Each element in the rayAngles array + /// determines a sublist of data to the observation. The sublist contains the observation data for a single cast. + /// The list is composed of the following: + /// 1. A one-hot encoding for detectable tags. For example, if DetectableTags.Length = n, the + /// first n elements of the sublist will be a one-hot encoding of the detectableTag that was hit, or + /// all zeroes otherwise. + /// 2. The 'numDetectableTags' element of the sublist will be 1 if the ray missed everything, or 0 if it hit + /// something (detectable or not). + /// 3. The 'numDetectableTags+1' element of the sublist will contain the normalized distance to the object + /// hit, or 1.0 if nothing was hit. + /// + /// Number of detectable tags + /// Ray index + /// Output buffer. The size must be equal to (numDetectableTags+2) * RayOutputs.Length + public void ToFloatArray(int numDetectableTags, int rayIndex, float[] buffer) + { + var bufferOffset = (numDetectableTags + 2) * rayIndex; + if (HitTaggedObject) + { + buffer[bufferOffset + HitTagIndex] = 1f; + } + buffer[bufferOffset + numDetectableTags] = HasHit ? 0f : 1f; + buffer[bufferOffset + numDetectableTags + 1] = HitFraction; + } + } + + /// + /// RayOutput for each ray that was cast. + /// + public RayOutput[] RayOutputs; + } + + /// + /// A sensor implementation that supports ray cast-based observations. + /// + public class RayPerceptionSensor : ISensor, IBuiltInSensor + { + float[] m_Observations; + ObservationSpec m_ObservationSpec; + string m_Name; + + RayPerceptionInput m_RayPerceptionInput; + RayPerceptionOutput m_RayPerceptionOutput; + + bool m_UseBatchedRaycasts; + + /// + /// Time.frameCount at the last time Update() was called. This is only used for display in gizmos. + /// + int m_DebugLastFrameCount; + + internal int DebugLastFrameCount + { + get { return m_DebugLastFrameCount; } + } + + /// + /// Creates the RayPerceptionSensor. + /// + /// The name of the sensor. + /// The inputs for the sensor. + public RayPerceptionSensor(string name, RayPerceptionInput rayInput) + { + m_Name = name; + m_RayPerceptionInput = rayInput; + m_UseBatchedRaycasts = rayInput.UseBatchedRaycasts; + + SetNumObservations(rayInput.OutputSize()); + + m_DebugLastFrameCount = Time.frameCount; + m_RayPerceptionOutput = new RayPerceptionOutput(); + } + + /// + /// The most recent raycast results. + /// + public RayPerceptionOutput RayPerceptionOutput + { + get { return m_RayPerceptionOutput; } + } + + void SetNumObservations(int numObservations) + { + m_ObservationSpec = ObservationSpec.Vector(numObservations); + m_Observations = new float[numObservations]; + } + + internal void SetRayPerceptionInput(RayPerceptionInput rayInput) + { + // Note that change the number of rays or tags doesn't directly call this, + // but changing them and then changing another field will. + if (m_RayPerceptionInput.OutputSize() != rayInput.OutputSize()) + { + Debug.Log( + "Changing the number of tags or rays at runtime is not " + + "supported and may cause errors in training or inference." + ); + // Changing the shape will probably break things downstream, but we can at least + // keep this consistent. + SetNumObservations(rayInput.OutputSize()); + } + m_RayPerceptionInput = rayInput; + } + + /// + /// Computes the ray perception observations and saves them to the provided + /// . + /// + /// Where the ray perception observations are written to. + /// The number of written observations. + public int Write(ObservationWriter writer) + { + using (TimerStack.Instance.Scoped("RayPerceptionSensor.Perceive")) + { + Array.Clear(m_Observations, 0, m_Observations.Length); + var numRays = m_RayPerceptionInput.Angles.Count; + var numDetectableTags = m_RayPerceptionInput.DetectableTags.Count; + + // For each ray, write the information to the observation buffer + for (var rayIndex = 0; rayIndex < numRays; rayIndex++) + { + m_RayPerceptionOutput.RayOutputs?[rayIndex].ToFloatArray(numDetectableTags, rayIndex, m_Observations); + } + + // Finally, add the observations to the ObservationWriter + writer.AddList(m_Observations); + } + return m_Observations.Length; + } + + /// + public void Update() + { + m_DebugLastFrameCount = Time.frameCount; + var numRays = m_RayPerceptionInput.Angles.Count; + + if (m_RayPerceptionOutput.RayOutputs == null || m_RayPerceptionOutput.RayOutputs.Length != numRays) + { + m_RayPerceptionOutput.RayOutputs = new RayPerceptionOutput.RayOutput[numRays]; + } + + if (m_UseBatchedRaycasts && m_RayPerceptionInput.CastType == RayPerceptionCastType.Cast3D) + { + PerceiveBatchedRays(ref m_RayPerceptionOutput.RayOutputs, m_RayPerceptionInput); + } + else + { + // For each ray, do the casting and save the results. + for (var rayIndex = 0; rayIndex < numRays; rayIndex++) + { + m_RayPerceptionOutput.RayOutputs[rayIndex] = PerceiveSingleRay(m_RayPerceptionInput, rayIndex); + } + } + } + + /// + public void Reset() { } + + /// + public ObservationSpec GetObservationSpec() + { + return m_ObservationSpec; + } + + /// + public string GetName() + { + return m_Name; + } + + /// + public virtual byte[] GetCompressedObservation() + { + return null; + } + + /// + public CompressionSpec GetCompressionSpec() + { + return CompressionSpec.Default(); + } + + /// + public BuiltInSensorType GetBuiltInSensorType() + { + return BuiltInSensorType.RayPerceptionSensor; + } + + /// + /// Evaluates the raycasts to be used as part of an observation of an agent. + /// + /// Input defining the rays that will be cast. + /// Use batched raycasts. + /// Output struct containing the raycast results. + public static RayPerceptionOutput Perceive(RayPerceptionInput input, bool batched) + { + RayPerceptionOutput output = new RayPerceptionOutput(); + output.RayOutputs = new RayPerceptionOutput.RayOutput[input.Angles.Count]; + + if (batched) + { + PerceiveBatchedRays(ref output.RayOutputs, input); + } + else + { + for (var rayIndex = 0; rayIndex < input.Angles.Count; rayIndex++) + { + output.RayOutputs[rayIndex] = PerceiveSingleRay(input, rayIndex); + } + } + + return output; + } + + /// + /// Evaluate the raycast results of all the rays from the RayPerceptionInput as a batch. + /// + /// Input + /// Ray index + internal static void PerceiveBatchedRays(ref RayPerceptionOutput.RayOutput[] batchedRaycastOutputs, RayPerceptionInput input) + { + var numRays = input.Angles.Count; + var results = new NativeArray(numRays, Allocator.TempJob); + var unscaledRayLength = input.RayLength; + var unscaledCastRadius = input.CastRadius; + + var raycastCommands = new NativeArray(unscaledCastRadius <= 0f ? numRays : 0, Allocator.TempJob); + var spherecastCommands = new NativeArray(unscaledCastRadius > 0f ? numRays : 0, Allocator.TempJob); + + // this is looped + + for (int i = 0; i < numRays; i++) + { + var extents = input.RayExtents(i); + var startPositionWorld = extents.StartPositionWorld; + var endPositionWorld = extents.EndPositionWorld; + + var rayDirection = endPositionWorld - startPositionWorld; + // If there is non-unity scale, |rayDirection| will be different from rayLength. + // We want to use this transformed ray length for determining cast length, hit fraction etc. + // We also it to scale up or down the sphere or circle radii + var scaledRayLength = rayDirection.magnitude; + // Avoid 0/0 if unscaledRayLength is 0 + var scaledCastRadius = unscaledRayLength > 0 ? + unscaledCastRadius * scaledRayLength / unscaledRayLength : + unscaledCastRadius; + + var queryParameters = QueryParameters.Default; + queryParameters.layerMask = input.LayerMask; + + var rayDirectionNormalized = rayDirection.normalized; + + if (scaledCastRadius > 0f) + { + spherecastCommands[i] = new SpherecastCommand(startPositionWorld, scaledCastRadius, rayDirectionNormalized, queryParameters, scaledRayLength); + } + else + { + raycastCommands[i] = new RaycastCommand(startPositionWorld, rayDirectionNormalized, queryParameters, scaledRayLength); + } + + batchedRaycastOutputs[i] = new RayPerceptionOutput.RayOutput + { + HitTaggedObject = false, + HitTagIndex = -1, + StartPositionWorld = startPositionWorld, + EndPositionWorld = endPositionWorld, + ScaledCastRadius = scaledCastRadius + }; + } + + if (unscaledCastRadius > 0f) + { + JobHandle handle = SpherecastCommand.ScheduleBatch(spherecastCommands, results, 1, 1, default(JobHandle)); + handle.Complete(); + } + else + { + JobHandle handle = RaycastCommand.ScheduleBatch(raycastCommands, results, 1, 1, default(JobHandle)); + handle.Complete(); + } + + for (int i = 0; i < results.Length; i++) + { + var castHit = results[i].collider != null; + var hitFraction = 1.0f; + GameObject hitObject = null; + float scaledRayLength; + float scaledCastRadius = batchedRaycastOutputs[i].ScaledCastRadius; + if (scaledCastRadius > 0f) + { + scaledRayLength = spherecastCommands[i].distance; + } + else + { + scaledRayLength = raycastCommands[i].distance; + } + + // hitFraction = castHit ? (scaledRayLength > 0 ? results[i].distance / scaledRayLength : 0.0f) : 1.0f; + // Debug.Log(results[i].distance); + hitFraction = castHit ? (scaledRayLength > 0 ? results[i].distance / scaledRayLength : 0.0f) : 1.0f; + hitObject = castHit ? results[i].collider.gameObject : null; + + if (castHit) + { + var numTags = input.DetectableTags?.Count ?? 0; + for (int j = 0; j < numTags; j++) + { + var tagsEqual = false; + try + { + var tag = input.DetectableTags[j]; + if (!string.IsNullOrEmpty(tag)) + { + tagsEqual = hitObject.CompareTag(tag); + } + } + catch (UnityException) + { + } + + if (tagsEqual) + { + batchedRaycastOutputs[i].HitTaggedObject = true; + batchedRaycastOutputs[i].HitTagIndex = j; + break; + } + } + } + + batchedRaycastOutputs[i].HasHit = castHit; + batchedRaycastOutputs[i].HitFraction = hitFraction; + batchedRaycastOutputs[i].HitGameObject = hitObject; + } + + results.Dispose(); + raycastCommands.Dispose(); + spherecastCommands.Dispose(); + } + + /// + /// Evaluate the raycast results of a single ray from the RayPerceptionInput. + /// + /// Input + /// Ray index + /// `RayOutput` result of a single raycast. + internal static RayPerceptionOutput.RayOutput PerceiveSingleRay( + RayPerceptionInput input, + int rayIndex + ) + { + var unscaledRayLength = input.RayLength; + var unscaledCastRadius = input.CastRadius; + + var extents = input.RayExtents(rayIndex); + var startPositionWorld = extents.StartPositionWorld; + var endPositionWorld = extents.EndPositionWorld; + + var rayDirection = endPositionWorld - startPositionWorld; + // If there is non-unity scale, |rayDirection| will be different from rayLength. + // We want to use this transformed ray length for determining cast length, hit fraction etc. + // We also it to scale up or down the sphere or circle radii + var scaledRayLength = rayDirection.magnitude; + // Avoid 0/0 if unscaledRayLength is 0 + var scaledCastRadius = unscaledRayLength > 0 ? + unscaledCastRadius * scaledRayLength / unscaledRayLength : + unscaledCastRadius; + + // Do the cast and assign the hit information for each detectable tag. + var castHit = false; + var hitFraction = 1.0f; + GameObject hitObject = null; + + if (input.CastType == RayPerceptionCastType.Cast3D) + { +#if MLA_UNITY_PHYSICS_MODULE + RaycastHit rayHit; + if (scaledCastRadius > 0f) + { + castHit = Physics.SphereCast(startPositionWorld, scaledCastRadius, rayDirection, out rayHit, + scaledRayLength, input.LayerMask); + } + else + { + castHit = Physics.Raycast(startPositionWorld, rayDirection, out rayHit, + scaledRayLength, input.LayerMask); + } + + // If scaledRayLength is 0, we still could have a hit with sphere casts (maybe?). + // To avoid 0/0, set the fraction to 0. + hitFraction = castHit ? (scaledRayLength > 0 ? rayHit.distance / scaledRayLength : 0.0f) : 1.0f; + hitObject = castHit ? rayHit.collider.gameObject : null; +#endif + } + else + { +#if MLA_UNITY_PHYSICS2D_MODULE + RaycastHit2D rayHit; + if (scaledCastRadius > 0f) + { + rayHit = Physics2D.CircleCast(startPositionWorld, scaledCastRadius, rayDirection, + scaledRayLength, input.LayerMask); + } + else + { + rayHit = Physics2D.Raycast(startPositionWorld, rayDirection, scaledRayLength, input.LayerMask); + } + + castHit = rayHit; + hitFraction = castHit ? rayHit.fraction : 1.0f; + hitObject = castHit ? rayHit.collider.gameObject : null; +#endif + } + + var rayOutput = new RayPerceptionOutput.RayOutput + { + HasHit = castHit, + HitFraction = hitFraction, + HitTaggedObject = false, + HitTagIndex = -1, + HitGameObject = hitObject, + StartPositionWorld = startPositionWorld, + EndPositionWorld = endPositionWorld, + ScaledCastRadius = scaledCastRadius + }; + + if (castHit) + { + // Find the index of the tag of the object that was hit. + var numTags = input.DetectableTags?.Count ?? 0; + for (var i = 0; i < numTags; i++) + { + var tagsEqual = false; + try + { + var tag = input.DetectableTags[i]; + if (!string.IsNullOrEmpty(tag)) + { + tagsEqual = hitObject.CompareTag(tag); + } + } + catch (UnityException) + { + // If the tag is null, empty, or not a valid tag, just ignore it. + } + + if (tagsEqual) + { + rayOutput.HitTaggedObject = true; + rayOutput.HitTagIndex = i; + break; + } + } + } + + + return rayOutput; + } + } +} diff --git a/com.unity.ml-agents/Runtime/Sensors/RayPerceptionSensor.cs.meta b/com.unity.ml-agents/Runtime/Sensors/RayPerceptionSensor.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..4c7247977c9cf9e1597f4af2199b3a850ca259d7 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Sensors/RayPerceptionSensor.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Sensors/RayPerceptionSensorComponent2D.cs b/com.unity.ml-agents/Runtime/Sensors/RayPerceptionSensorComponent2D.cs new file mode 100644 index 0000000000000000000000000000000000000000..3abee3d46b1f60d7166322d5a614bffa252d48f6 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Sensors/RayPerceptionSensorComponent2D.cs @@ -0,0 +1,17 @@ +using UnityEngine; + +namespace Unity.MLAgents.Sensors +{ + /// + /// A component for 2D Ray Perception. + /// + [AddComponentMenu("ML Agents/Ray Perception Sensor 2D", (int)MenuGroup.Sensors)] + public class RayPerceptionSensorComponent2D : RayPerceptionSensorComponentBase + { + /// + public override RayPerceptionCastType GetCastType() + { + return RayPerceptionCastType.Cast2D; + } + } +} diff --git a/com.unity.ml-agents/Runtime/Sensors/RayPerceptionSensorComponent2D.cs.meta b/com.unity.ml-agents/Runtime/Sensors/RayPerceptionSensorComponent2D.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..947a0904d37d57be206e26043c9746fc0f8836ae Binary files /dev/null and b/com.unity.ml-agents/Runtime/Sensors/RayPerceptionSensorComponent2D.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Sensors/RayPerceptionSensorComponent3D.cs b/com.unity.ml-agents/Runtime/Sensors/RayPerceptionSensorComponent3D.cs new file mode 100644 index 0000000000000000000000000000000000000000..34bde4814058500dab7f05a418b35cbe9a518b31 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Sensors/RayPerceptionSensorComponent3D.cs @@ -0,0 +1,58 @@ +using UnityEngine; +using UnityEngine.Serialization; + +namespace Unity.MLAgents.Sensors +{ + /// + /// A component for 3D Ray Perception. + /// + [AddComponentMenu("ML Agents/Ray Perception Sensor 3D", (int)MenuGroup.Sensors)] + public class RayPerceptionSensorComponent3D : RayPerceptionSensorComponentBase + { + [HideInInspector, SerializeField, FormerlySerializedAs("startVerticalOffset")] + [Range(-10f, 10f)] + [Tooltip("Ray start is offset up or down by this amount.")] + float m_StartVerticalOffset; + + /// + /// Ray start is offset up or down by this amount. + /// + public float StartVerticalOffset + { + get => m_StartVerticalOffset; + set { m_StartVerticalOffset = value; UpdateSensor(); } + } + + [HideInInspector, SerializeField, FormerlySerializedAs("endVerticalOffset")] + [Range(-10f, 10f)] + [Tooltip("Ray end is offset up or down by this amount.")] + float m_EndVerticalOffset; + + /// + /// Ray end is offset up or down by this amount. + /// + public float EndVerticalOffset + { + get => m_EndVerticalOffset; + set { m_EndVerticalOffset = value; UpdateSensor(); } + } + + /// + public override RayPerceptionCastType GetCastType() + { + return RayPerceptionCastType.Cast3D; + } + + /// + public override float GetStartVerticalOffset() + { + return StartVerticalOffset; + } + + /// + public override float GetEndVerticalOffset() + { + return EndVerticalOffset; + } + } +} diff --git a/com.unity.ml-agents/Runtime/Sensors/RayPerceptionSensorComponent3D.cs.meta b/com.unity.ml-agents/Runtime/Sensors/RayPerceptionSensorComponent3D.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..51ec4e5b168289fe70c86260231861f3033795b7 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Sensors/RayPerceptionSensorComponent3D.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Sensors/RayPerceptionSensorComponentBase.cs b/com.unity.ml-agents/Runtime/Sensors/RayPerceptionSensorComponentBase.cs new file mode 100644 index 0000000000000000000000000000000000000000..dbb93a2ea0d1f2b2b2df335ba8052bd9d427ee4a --- /dev/null +++ b/com.unity.ml-agents/Runtime/Sensors/RayPerceptionSensorComponentBase.cs @@ -0,0 +1,404 @@ +using System; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.Serialization; + +namespace Unity.MLAgents.Sensors +{ + /// + /// A base class to support sensor components for raycast-based sensors. + /// + public abstract class RayPerceptionSensorComponentBase : SensorComponent + { + [HideInInspector, SerializeField, FormerlySerializedAs("sensorName")] + string m_SensorName = "RayPerceptionSensor"; + + /// + /// The name of the Sensor that this component wraps. + /// Note that changing this at runtime does not affect how the Agent sorts the sensors. + /// + public string SensorName + { + get { return m_SensorName; } + set { m_SensorName = value; } + } + + [SerializeField, FormerlySerializedAs("detectableTags")] + [Tooltip("List of tags in the scene to compare against.")] + List m_DetectableTags; + + /// + /// List of tags in the scene to compare against. + /// Note that this should not be changed at runtime. + /// + public List DetectableTags + { + get { return m_DetectableTags; } + set { m_DetectableTags = value; } + } + + [HideInInspector, SerializeField, FormerlySerializedAs("raysPerDirection")] + [Range(0, 50)] + [Tooltip("Number of rays to the left and right of center.")] + int m_RaysPerDirection = 3; + + /// + /// Number of rays to the left and right of center. + /// Note that this should not be changed at runtime. + /// + public int RaysPerDirection + { + get { return m_RaysPerDirection; } + // Note: can't change at runtime + set { m_RaysPerDirection = value; } + } + + [HideInInspector, SerializeField, FormerlySerializedAs("maxRayDegrees")] + [Range(0, 180)] + [Tooltip("Cone size for rays. Using 90 degrees will cast rays to the left and right. " + + "Greater than 90 degrees will go backwards.")] + float m_MaxRayDegrees = 70; + + /// + /// Cone size for rays. Using 90 degrees will cast rays to the left and right. + /// Greater than 90 degrees will go backwards. + /// + public float MaxRayDegrees + { + get => m_MaxRayDegrees; + set { m_MaxRayDegrees = value; UpdateSensor(); } + } + + [HideInInspector, SerializeField, FormerlySerializedAs("sphereCastRadius")] + [Range(0f, 10f)] + [Tooltip("Radius of sphere to cast. Set to zero for raycasts.")] + float m_SphereCastRadius = 0.5f; + + /// + /// Radius of sphere to cast. Set to zero for raycasts. + /// + public float SphereCastRadius + { + get => m_SphereCastRadius; + set { m_SphereCastRadius = value; UpdateSensor(); } + } + + [HideInInspector, SerializeField, FormerlySerializedAs("rayLength")] + [Range(1, 1000)] + [Tooltip("Length of the rays to cast.")] + float m_RayLength = 20f; + + /// + /// Length of the rays to cast. + /// + public float RayLength + { + get => m_RayLength; + set { m_RayLength = value; UpdateSensor(); } + } + + // The value of the default layers. + const int k_PhysicsDefaultLayers = -5; + [HideInInspector, SerializeField, FormerlySerializedAs("rayLayerMask")] + [Tooltip("Controls which layers the rays can hit.")] + LayerMask m_RayLayerMask = k_PhysicsDefaultLayers; + + /// + /// Controls which layers the rays can hit. + /// + public LayerMask RayLayerMask + { + get => m_RayLayerMask; + set { m_RayLayerMask = value; UpdateSensor(); } + } + + [HideInInspector, SerializeField, FormerlySerializedAs("observationStacks")] + [Range(1, 50)] + [Tooltip("Number of raycast results that will be stacked before being fed to the neural network.")] + int m_ObservationStacks = 1; + + /// + /// Whether to stack previous observations. Using 1 means no previous observations. + /// Note that changing this after the sensor is created has no effect. + /// + public int ObservationStacks + { + get { return m_ObservationStacks; } + set { m_ObservationStacks = value; } + } + + /// + /// Disable to provide the rays in left to right order + /// + [HideInInspector, SerializeField] + [Tooltip("Disable to provide the rays in left to right order. Warning: Alternating order will be deprecated, disable it to ensure compatibility with future versions of ML-Agents.")] + public bool m_AlternatingRayOrder = true; + + /// + /// Determines how the rays are ordered. By default the ordering is as follows: middle ray is first; + /// then alternates outward adding rays to the left and right. If set to false, then the rays are + /// ordered from left to right (viewed from above) which is more amenable to processing with + /// conv nets. + /// This property will be deprecated with the next major version update and the left to right ordering + /// will be used thereafter. + /// + public bool AlternatingRayOrder + { + get { return m_AlternatingRayOrder; } + set { m_AlternatingRayOrder = value; } + } + + /// + /// Determines whether to use batched raycasts and the jobs system. Default = false. + /// + [HideInInspector, SerializeField] + [Tooltip("Enable to use batched raycasts and the jobs system.")] + public bool m_UseBatchedRaycasts = false; + + /// + /// Determines whether to use batched raycasts and the jobs system. Default = false. + /// + public bool UseBatchedRaycasts + { + get { return m_UseBatchedRaycasts; } + set { m_UseBatchedRaycasts = value; } + } + + + /// + /// Color to code a ray that hits another object. + /// + [HideInInspector] + [SerializeField] + [Header("Debug Gizmos", order = 999)] + internal Color rayHitColor = Color.red; + + /// + /// Color to code a ray that avoid or misses all other objects. + /// + [HideInInspector] + [SerializeField] + internal Color rayMissColor = Color.white; + + [NonSerialized] + RayPerceptionSensor m_RaySensor; + + /// + /// Get the RayPerceptionSensor that was created. + /// + public RayPerceptionSensor RaySensor + { + get => m_RaySensor; + } + + /// + /// Returns the for the associated raycast sensor. + /// + /// `RayPerceptionCastType` for the associated raycast sensor. + public abstract RayPerceptionCastType GetCastType(); + + /// + /// Returns the amount that the ray start is offset up or down by. + /// + /// The amount that the ray start is offset up or down by. + public virtual float GetStartVerticalOffset() + { + return 0f; + } + + /// + /// Returns the amount that the ray end is offset up or down by. + /// + /// The amount that the ray end is offset up or down by. + public virtual float GetEndVerticalOffset() + { + return 0f; + } + + /// + /// Returns an initialized raycast sensor. + /// + /// Initialized `ISensor` array. + public override ISensor[] CreateSensors() + { + var rayPerceptionInput = GetRayPerceptionInput(); + + m_RaySensor = new RayPerceptionSensor(m_SensorName, rayPerceptionInput); + + if (ObservationStacks != 1) + { + var stackingSensor = new StackingSensor(m_RaySensor, ObservationStacks); + return new ISensor[] { stackingSensor }; + } + + return new ISensor[] { m_RaySensor }; + } + + /// + /// Returns the specific ray angles given the number of rays per direction and the + /// cone size for the rays. + /// + /// Number of rays to the left and right of center. + /// + /// Cone size for rays. Using 90 degrees will cast rays to the left and right. + /// Greater than 90 degrees will go backwards. + /// Orders the rays starting with the centermost and alternating to the left and right. + /// Should be deprecated with a future major version release (doing so will break existing + /// models). + /// + /// The corresponding ray angles. + internal static float[] GetRayAnglesAlternating(int raysPerDirection, float maxRayDegrees) + { + // Example: + // { 90, 90 - delta, 90 + delta, 90 - 2*delta, 90 + 2*delta } + var anglesOut = new float[2 * raysPerDirection + 1]; + var delta = maxRayDegrees / raysPerDirection; + anglesOut[0] = 90f; + for (var i = 0; i < raysPerDirection; i++) + { + anglesOut[2 * i + 1] = 90 - (i + 1) * delta; + anglesOut[2 * i + 2] = 90 + (i + 1) * delta; + } + return anglesOut; + } + + /// + /// Returns the specific ray angles given the number of rays per direction and the + /// cone size for the rays. + /// + /// Number of rays to the left and right of center. + /// + /// Cone size for rays. Using 90 degrees will cast rays to the left and right. + /// Greater than 90 degrees will go backwards. + /// Orders the rays from the left-most to the right-most which makes using a convolution + /// in the model easier. + /// + /// The corresponding ray angles. + internal static float[] GetRayAngles(int raysPerDirection, float maxRayDegrees) + { + // Example: + // { 90 - 3*delta, 90 - 2*delta, ..., 90, 90 + delta, ..., 90 + 3*delta } + var anglesOut = new float[2 * raysPerDirection + 1]; + var delta = maxRayDegrees / raysPerDirection; + + for (var i = 0; i < 2 * raysPerDirection + 1; i++) + { + anglesOut[i] = 90 + (i - raysPerDirection) * delta; + } + + return anglesOut; + } + + /// + /// Get the RayPerceptionInput that is used by the . + /// + /// `RayPerceptionInput` that is used by the sensor. + public RayPerceptionInput GetRayPerceptionInput() + { + var rayAngles = m_AlternatingRayOrder ? + GetRayAnglesAlternating(RaysPerDirection, MaxRayDegrees) : + GetRayAngles(RaysPerDirection, MaxRayDegrees); + + var rayPerceptionInput = new RayPerceptionInput(); + rayPerceptionInput.RayLength = RayLength; + rayPerceptionInput.DetectableTags = DetectableTags; + rayPerceptionInput.Angles = rayAngles; + rayPerceptionInput.StartOffset = GetStartVerticalOffset(); + rayPerceptionInput.EndOffset = GetEndVerticalOffset(); + rayPerceptionInput.CastRadius = SphereCastRadius; + rayPerceptionInput.Transform = transform; + rayPerceptionInput.CastType = GetCastType(); + rayPerceptionInput.LayerMask = RayLayerMask; + rayPerceptionInput.UseBatchedRaycasts = UseBatchedRaycasts; + + return rayPerceptionInput; + } + + internal void UpdateSensor() + { + if (m_RaySensor != null) + { + var rayInput = GetRayPerceptionInput(); + m_RaySensor.SetRayPerceptionInput(rayInput); + } + } + + internal int SensorObservationAge() + { + if (m_RaySensor != null) + { + return Time.frameCount - m_RaySensor.DebugLastFrameCount; + } + + return 0; + } + + void OnDrawGizmosSelected() + { + if (m_RaySensor?.RayPerceptionOutput?.RayOutputs != null) + { + // If we have cached debug info from the sensor, draw that. + // Draw "old" observations in a lighter color. + // Since the agent may not step every frame, this helps de-emphasize "stale" hit information. + var alpha = Mathf.Pow(.5f, SensorObservationAge()); + + foreach (var rayInfo in m_RaySensor.RayPerceptionOutput.RayOutputs) + { + DrawRaycastGizmos(rayInfo, alpha); + } + } + else + { + var rayInput = GetRayPerceptionInput(); + // We don't actually need the tags here, since they don't affect the display of the rays. + // Additionally, the user might be in the middle of typing the tag name when this is called, + // and there's no way to turn off the "Tag ... is not defined" error logs. + // So just don't use any tags here. + rayInput.DetectableTags = null; + if (m_UseBatchedRaycasts && rayInput.CastType == RayPerceptionCastType.Cast3D) + { + // TODO add call to PerceiveBatchedRays() + var rayOutputs = new RayPerceptionOutput.RayOutput[rayInput.Angles.Count]; + RayPerceptionSensor.PerceiveBatchedRays(ref rayOutputs, rayInput); + for (var rayIndex = 0; rayIndex < rayInput.Angles.Count; rayIndex++) + { + DrawRaycastGizmos(rayOutputs[rayIndex]); + } + } + else + { + for (var rayIndex = 0; rayIndex < rayInput.Angles.Count; rayIndex++) + { + var rayOutput = RayPerceptionSensor.PerceiveSingleRay(rayInput, rayIndex); + DrawRaycastGizmos(rayOutput); + } + } + } + } + + /// + /// Draw the debug information from the sensor (if available). + /// + void DrawRaycastGizmos(RayPerceptionOutput.RayOutput rayOutput, float alpha = 1.0f) + { + var startPositionWorld = rayOutput.StartPositionWorld; + var endPositionWorld = rayOutput.EndPositionWorld; + var rayDirection = endPositionWorld - startPositionWorld; + rayDirection *= rayOutput.HitFraction; + + // hit fraction ^2 will shift "far" hits closer to the hit color + var lerpT = rayOutput.HitFraction * rayOutput.HitFraction; + var color = Color.Lerp(rayHitColor, rayMissColor, lerpT); + color.a *= alpha; + Gizmos.color = color; + Gizmos.DrawRay(startPositionWorld, rayDirection); + + // Draw the hit point as a sphere. If using rays to cast (0 radius), use a small sphere. + if (rayOutput.HasHit) + { + var hitRadius = Mathf.Max(rayOutput.ScaledCastRadius, .05f); + Gizmos.DrawWireSphere(startPositionWorld + rayDirection, hitRadius); + } + } + } +} diff --git a/com.unity.ml-agents/Runtime/Sensors/RayPerceptionSensorComponentBase.cs.meta b/com.unity.ml-agents/Runtime/Sensors/RayPerceptionSensorComponentBase.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..97f40e582f285a100a16caaf13d5b9c23c407688 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Sensors/RayPerceptionSensorComponentBase.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Sensors/Reflection.meta b/com.unity.ml-agents/Runtime/Sensors/Reflection.meta new file mode 100644 index 0000000000000000000000000000000000000000..fb7288f7178a8f35ef31ae11979f24520cef13ad Binary files /dev/null and b/com.unity.ml-agents/Runtime/Sensors/Reflection.meta differ diff --git a/com.unity.ml-agents/Runtime/Sensors/Reflection/BoolReflectionSensor.cs b/com.unity.ml-agents/Runtime/Sensors/Reflection/BoolReflectionSensor.cs new file mode 100644 index 0000000000000000000000000000000000000000..606656ecb3976f14252d9917c42ce8658afb7c3d --- /dev/null +++ b/com.unity.ml-agents/Runtime/Sensors/Reflection/BoolReflectionSensor.cs @@ -0,0 +1,19 @@ +namespace Unity.MLAgents.Sensors.Reflection +{ + /// + /// Sensor that wraps a boolean field or property of an object, and returns + /// that as an observation. + /// + internal class BoolReflectionSensor : ReflectionSensorBase + { + public BoolReflectionSensor(ReflectionSensorInfo reflectionSensorInfo) + : base(reflectionSensorInfo, 1) + { } + + internal override void WriteReflectedField(ObservationWriter writer) + { + var boolVal = (System.Boolean)GetReflectedValue(); + writer[0] = boolVal ? 1.0f : 0.0f; + } + } +} diff --git a/com.unity.ml-agents/Runtime/Sensors/Reflection/BoolReflectionSensor.cs.meta b/com.unity.ml-agents/Runtime/Sensors/Reflection/BoolReflectionSensor.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..5cac420f1160c36ccd0acb1651360aa6be3466a0 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Sensors/Reflection/BoolReflectionSensor.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Sensors/Reflection/EnumReflectionSensor.cs b/com.unity.ml-agents/Runtime/Sensors/Reflection/EnumReflectionSensor.cs new file mode 100644 index 0000000000000000000000000000000000000000..2d92df0369e3569d25c17685651eaf1b2b112242 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Sensors/Reflection/EnumReflectionSensor.cs @@ -0,0 +1,61 @@ +using System; + +namespace Unity.MLAgents.Sensors.Reflection +{ + internal class EnumReflectionSensor : ReflectionSensorBase + { + Array m_Values; + bool m_IsFlags; + + internal EnumReflectionSensor(ReflectionSensorInfo reflectionSensorInfo) + : base(reflectionSensorInfo, GetEnumObservationSize(reflectionSensorInfo.GetMemberType())) + { + var memberType = reflectionSensorInfo.GetMemberType(); + m_Values = Enum.GetValues(memberType); + m_IsFlags = memberType.IsDefined(typeof(FlagsAttribute), false); + } + + internal override void WriteReflectedField(ObservationWriter writer) + { + // Write the enum value as a one-hot encoding. + // Note that unknown enum values will record all 0's. + // Flags will get treated as a sequence of bools. + var enumValue = (Enum)GetReflectedValue(); + + int i = 0; + foreach (var val in m_Values) + { + if (m_IsFlags) + { + if (enumValue.HasFlag((Enum)val)) + { + writer[i] = 1.0f; + } + else + { + writer[i] = 0.0f; + } + } + else + { + if (val.Equals(enumValue)) + { + writer[i] = 1.0f; + } + else + { + writer[i] = 0.0f; + } + } + i++; + } + } + + internal static int GetEnumObservationSize(Type t) + { + var values = Enum.GetValues(t); + // Account for all enum values + return values.Length; + } + } +} diff --git a/com.unity.ml-agents/Runtime/Sensors/Reflection/EnumReflectionSensor.cs.meta b/com.unity.ml-agents/Runtime/Sensors/Reflection/EnumReflectionSensor.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..d42cce5521050af2dab0678985aff59be92ce5f0 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Sensors/Reflection/EnumReflectionSensor.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Sensors/Reflection/FloatReflectionSensor.cs b/com.unity.ml-agents/Runtime/Sensors/Reflection/FloatReflectionSensor.cs new file mode 100644 index 0000000000000000000000000000000000000000..a488a9ed5c280a6c5a47c8af9272c5800b77ce06 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Sensors/Reflection/FloatReflectionSensor.cs @@ -0,0 +1,19 @@ +namespace Unity.MLAgents.Sensors.Reflection +{ + /// + /// Sensor that wraps a float field or property of an object, and returns + /// that as an observation. + /// + internal class FloatReflectionSensor : ReflectionSensorBase + { + public FloatReflectionSensor(ReflectionSensorInfo reflectionSensorInfo) + : base(reflectionSensorInfo, 1) + { } + + internal override void WriteReflectedField(ObservationWriter writer) + { + var floatVal = (System.Single)GetReflectedValue(); + writer[0] = floatVal; + } + } +} diff --git a/com.unity.ml-agents/Runtime/Sensors/Reflection/FloatReflectionSensor.cs.meta b/com.unity.ml-agents/Runtime/Sensors/Reflection/FloatReflectionSensor.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..2de8b18c7c983ba42f393bbc66b0a9437f5281b0 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Sensors/Reflection/FloatReflectionSensor.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Sensors/Reflection/IntReflectionSensor.cs b/com.unity.ml-agents/Runtime/Sensors/Reflection/IntReflectionSensor.cs new file mode 100644 index 0000000000000000000000000000000000000000..6c1b10f45a4ae94ffdb4ce9b6dbd7881eb92f63f --- /dev/null +++ b/com.unity.ml-agents/Runtime/Sensors/Reflection/IntReflectionSensor.cs @@ -0,0 +1,19 @@ +namespace Unity.MLAgents.Sensors.Reflection +{ + /// + /// Sensor that wraps an integer field or property of an object, and returns + /// that as an observation. + /// + internal class IntReflectionSensor : ReflectionSensorBase + { + public IntReflectionSensor(ReflectionSensorInfo reflectionSensorInfo) + : base(reflectionSensorInfo, 1) + { } + + internal override void WriteReflectedField(ObservationWriter writer) + { + var intVal = (System.Int32)GetReflectedValue(); + writer[0] = intVal; + } + } +} diff --git a/com.unity.ml-agents/Runtime/Sensors/Reflection/IntReflectionSensor.cs.meta b/com.unity.ml-agents/Runtime/Sensors/Reflection/IntReflectionSensor.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..a07726937f76c77df0dfdba7d1816853bc028466 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Sensors/Reflection/IntReflectionSensor.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Sensors/Reflection/ObservableAttribute.cs b/com.unity.ml-agents/Runtime/Sensors/Reflection/ObservableAttribute.cs new file mode 100644 index 0000000000000000000000000000000000000000..8bde8e4b7f7f2ec9b8b0ef4a00a7e79849fbe532 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Sensors/Reflection/ObservableAttribute.cs @@ -0,0 +1,287 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using UnityEngine; + +namespace Unity.MLAgents.Sensors.Reflection +{ + /// + /// Specify that a field or property should be used to generate observations for an Agent. + /// For each field or property that uses ObservableAttribute, a corresponding + /// will be created during Agent initialization, and this + /// sensor will read the values during training and inference. + /// + /// + /// ObservableAttribute is intended to make initial setup of an Agent easier. Because it + /// uses reflection to read the values of fields and properties at runtime, this may + /// be much slower than reading the values directly. If the performance of + /// ObservableAttribute is an issue, you can get the same functionality by overriding + /// or creating a custom + /// implementation to read the values without reflection. + /// + /// Note that you do not need to adjust the VectorObservationSize in + /// when adding ObservableAttribute + /// to fields or properties. + /// + /// + /// + /// This sample class will produce two observations, one for the m_Health field, and one + /// for the HealthPercent property. + /// + /// + /// using Unity.MLAgents; + /// using Unity.MLAgents.Sensors.Reflection; + /// + /// public class MyAgent : Agent + /// { + /// [Observable] + /// int m_Health; + /// + /// [Observable] + /// float HealthPercent + /// { + /// get => return 100.0f * m_Health / float(m_MaxHealth); + /// } + /// } + /// + /// + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] + public class ObservableAttribute : Attribute + { + string m_Name; + int m_NumStackedObservations; + + /// + /// Default binding flags used for reflection of members and properties. + /// + const BindingFlags k_BindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; + + /// + /// Supported types and their observation sizes and corresponding sensor type. + /// + static readonly Dictionary s_TypeToSensorInfo = new Dictionary() + { + {typeof(int), (1, typeof(IntReflectionSensor))}, + {typeof(bool), (1, typeof(BoolReflectionSensor))}, + {typeof(float), (1, typeof(FloatReflectionSensor))}, + + {typeof(Vector2), (2, typeof(Vector2ReflectionSensor))}, + {typeof(Vector3), (3, typeof(Vector3ReflectionSensor))}, + {typeof(Vector4), (4, typeof(Vector4ReflectionSensor))}, + {typeof(Quaternion), (4, typeof(QuaternionReflectionSensor))}, + }; + + /// + /// ObservableAttribute constructor. + /// + /// Optional override for the sensor name. Note that all sensors for an Agent + /// must have a unique name. + /// Number of frames to concatenate observations from. + public ObservableAttribute(string name = null, int numStackedObservations = 1) + { + m_Name = name; + m_NumStackedObservations = numStackedObservations; + } + + /// + /// Returns a FieldInfo for all fields that have an ObservableAttribute + /// + /// Object being reflected + /// Whether to exclude inherited properties or not. + /// `FieldInfo` for all fields that have an O`bservableAttribute`. + static IEnumerable<(FieldInfo, ObservableAttribute)> GetObservableFields(object o, bool excludeInherited) + { + // TODO cache these (and properties) by type, so that we only have to reflect once. + var bindingFlags = k_BindingFlags | (excludeInherited ? BindingFlags.DeclaredOnly : 0); + var fields = o.GetType().GetFields(bindingFlags); + foreach (var field in fields) + { + var attr = (ObservableAttribute)GetCustomAttribute(field, typeof(ObservableAttribute)); + if (attr != null) + { + yield return (field, attr); + } + } + } + + /// + /// Returns a PropertyInfo for all fields that have an ObservableAttribute + /// + /// Object being reflected + /// Whether to exclude inherited properties or not. + /// `PropertyInfo` for all fields that have an `ObservableAttribute`. + static IEnumerable<(PropertyInfo, ObservableAttribute)> GetObservableProperties(object o, bool excludeInherited) + { + var bindingFlags = k_BindingFlags | (excludeInherited ? BindingFlags.DeclaredOnly : 0); + var properties = o.GetType().GetProperties(bindingFlags); + foreach (var prop in properties) + { + var attr = (ObservableAttribute)GetCustomAttribute(prop, typeof(ObservableAttribute)); + if (attr != null) + { + yield return (prop, attr); + } + } + } + + /// + /// Creates sensors for each field and property with ObservableAttribute. + /// + /// Object being reflected + /// Whether to exclude inherited properties or not. + /// Corresponding list of sensors. + internal static List CreateObservableSensors(object o, bool excludeInherited) + { + var sensorsOut = new List(); + foreach (var (field, attr) in GetObservableFields(o, excludeInherited)) + { + var sensor = CreateReflectionSensor(o, field, null, attr); + if (sensor != null) + { + sensorsOut.Add(sensor); + } + } + + foreach (var (prop, attr) in GetObservableProperties(o, excludeInherited)) + { + if (!prop.CanRead) + { + // Skip unreadable properties. + continue; + } + var sensor = CreateReflectionSensor(o, null, prop, attr); + if (sensor != null) + { + sensorsOut.Add(sensor); + } + } + + return sensorsOut; + } + + /// + /// Create the ISensor for either the field or property on the provided object. + /// If the data type is unsupported, or the property is write-only, returns null. + /// + /// + /// + /// + /// + /// The created `ISensor`. + /// + static ISensor CreateReflectionSensor(object o, FieldInfo fieldInfo, PropertyInfo propertyInfo, ObservableAttribute observableAttribute) + { + string memberName; + string declaringTypeName; + Type memberType; + if (fieldInfo != null) + { + declaringTypeName = fieldInfo.DeclaringType.Name; + memberName = fieldInfo.Name; + memberType = fieldInfo.FieldType; + } + else + { + declaringTypeName = propertyInfo.DeclaringType.Name; + memberName = propertyInfo.Name; + memberType = propertyInfo.PropertyType; + } + + if (!s_TypeToSensorInfo.ContainsKey(memberType) && !memberType.IsEnum) + { + // For unsupported types, return null and we'll filter them out later. + return null; + } + + string sensorName; + if (string.IsNullOrEmpty(observableAttribute.m_Name)) + { + sensorName = $"ObservableAttribute:{declaringTypeName}.{memberName}"; + } + else + { + sensorName = observableAttribute.m_Name; + } + + var reflectionSensorInfo = new ReflectionSensorInfo + { + Object = o, + FieldInfo = fieldInfo, + PropertyInfo = propertyInfo, + ObservableAttribute = observableAttribute, + SensorName = sensorName + }; + + ISensor sensor = null; + if (memberType.IsEnum) + { + sensor = new EnumReflectionSensor(reflectionSensorInfo); + } + else + { + var (_, sensorType) = s_TypeToSensorInfo[memberType]; + sensor = (ISensor)Activator.CreateInstance(sensorType, reflectionSensorInfo); + } + + // Wrap the base sensor in a StackingSensor if we're using stacking. + if (observableAttribute.m_NumStackedObservations > 1) + { + return new StackingSensor(sensor, observableAttribute.m_NumStackedObservations); + } + + return sensor; + } + + /// + /// Gets the sum of the observation sizes of the Observable fields and properties on an object. + /// Also appends errors to the errorsOut array. + /// + /// + /// + /// + /// The total observation size. + internal static int GetTotalObservationSize(object o, bool excludeInherited, List errorsOut) + { + int sizeOut = 0; + foreach (var (field, attr) in GetObservableFields(o, excludeInherited)) + { + if (s_TypeToSensorInfo.ContainsKey(field.FieldType)) + { + var (obsSize, _) = s_TypeToSensorInfo[field.FieldType]; + sizeOut += obsSize * attr.m_NumStackedObservations; + } + else if (field.FieldType.IsEnum) + { + sizeOut += EnumReflectionSensor.GetEnumObservationSize(field.FieldType); + } + else + { + errorsOut.Add($"Unsupported Observable type {field.FieldType.Name} on field {field.Name}"); + } + } + + foreach (var (prop, attr) in GetObservableProperties(o, excludeInherited)) + { + if (!prop.CanRead) + { + errorsOut.Add($"Observable property {prop.Name} is write-only."); + } + else if (s_TypeToSensorInfo.ContainsKey(prop.PropertyType)) + { + var (obsSize, _) = s_TypeToSensorInfo[prop.PropertyType]; + sizeOut += obsSize * attr.m_NumStackedObservations; + } + else if (prop.PropertyType.IsEnum) + { + sizeOut += EnumReflectionSensor.GetEnumObservationSize(prop.PropertyType); + } + else + { + errorsOut.Add($"Unsupported Observable type {prop.PropertyType.Name} on property {prop.Name}"); + } + } + + return sizeOut; + } + } +} diff --git a/com.unity.ml-agents/Runtime/Sensors/Reflection/ObservableAttribute.cs.meta b/com.unity.ml-agents/Runtime/Sensors/Reflection/ObservableAttribute.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..41659283dac1a5073cf4889845e1f926310cc8da Binary files /dev/null and b/com.unity.ml-agents/Runtime/Sensors/Reflection/ObservableAttribute.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Sensors/Reflection/QuaternionReflectionSensor.cs b/com.unity.ml-agents/Runtime/Sensors/Reflection/QuaternionReflectionSensor.cs new file mode 100644 index 0000000000000000000000000000000000000000..5cd92ee68fecde0e84ab28964c7b129143cec2fa --- /dev/null +++ b/com.unity.ml-agents/Runtime/Sensors/Reflection/QuaternionReflectionSensor.cs @@ -0,0 +1,19 @@ +namespace Unity.MLAgents.Sensors.Reflection +{ + /// + /// Sensor that wraps a quaternion field or property of an object, and returns + /// that as an observation. + /// + internal class QuaternionReflectionSensor : ReflectionSensorBase + { + public QuaternionReflectionSensor(ReflectionSensorInfo reflectionSensorInfo) + : base(reflectionSensorInfo, 4) + { } + + internal override void WriteReflectedField(ObservationWriter writer) + { + var quatVal = (UnityEngine.Quaternion)GetReflectedValue(); + writer.Add(quatVal); + } + } +} diff --git a/com.unity.ml-agents/Runtime/Sensors/Reflection/QuaternionReflectionSensor.cs.meta b/com.unity.ml-agents/Runtime/Sensors/Reflection/QuaternionReflectionSensor.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..f3970e6b51331ac9047eeab752e4a3e5f68ab2fb Binary files /dev/null and b/com.unity.ml-agents/Runtime/Sensors/Reflection/QuaternionReflectionSensor.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Sensors/Reflection/ReflectionSensorBase.cs b/com.unity.ml-agents/Runtime/Sensors/Reflection/ReflectionSensorBase.cs new file mode 100644 index 0000000000000000000000000000000000000000..49aadfe5956713adc8c9f0d730c20546c78e4097 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Sensors/Reflection/ReflectionSensorBase.cs @@ -0,0 +1,111 @@ +using System; +using System.Reflection; + +namespace Unity.MLAgents.Sensors.Reflection +{ + /// + /// Construction info for a ReflectionSensorBase. + /// + internal struct ReflectionSensorInfo + { + public object Object; + + public FieldInfo FieldInfo; + public PropertyInfo PropertyInfo; + public ObservableAttribute ObservableAttribute; + public string SensorName; + + public Type GetMemberType() + { + return FieldInfo != null ? FieldInfo.FieldType : PropertyInfo.PropertyType; + } + } + + /// + /// Abstract base class for reflection-based sensors. + /// + internal abstract class ReflectionSensorBase : ISensor, IBuiltInSensor + { + protected object m_Object; + + // Exactly one of m_FieldInfo and m_PropertyInfo should be non-null. + protected FieldInfo m_FieldInfo; + protected PropertyInfo m_PropertyInfo; + + // Not currently used, but might want later. + protected ObservableAttribute m_ObservableAttribute; + + // Cached sensor names and shapes. + string m_SensorName; + ObservationSpec m_ObservationSpec; + int m_NumFloats; + + public ReflectionSensorBase(ReflectionSensorInfo reflectionSensorInfo, int size) + { + m_Object = reflectionSensorInfo.Object; + m_FieldInfo = reflectionSensorInfo.FieldInfo; + m_PropertyInfo = reflectionSensorInfo.PropertyInfo; + m_ObservableAttribute = reflectionSensorInfo.ObservableAttribute; + m_SensorName = reflectionSensorInfo.SensorName; + m_ObservationSpec = ObservationSpec.Vector(size); + m_NumFloats = size; + } + + /// + public ObservationSpec GetObservationSpec() + { + return m_ObservationSpec; + } + + /// + public int Write(ObservationWriter writer) + { + WriteReflectedField(writer); + return m_NumFloats; + } + + internal abstract void WriteReflectedField(ObservationWriter writer); + + /// + /// Get either the reflected field, or return the reflected property. + /// This should be used by implementations in their WriteReflectedField() method. + /// + /// `object` representing either the reflected field, or return the reflected property. + protected object GetReflectedValue() + { + return m_FieldInfo != null ? + m_FieldInfo.GetValue(m_Object) : + m_PropertyInfo.GetMethod.Invoke(m_Object, null); + } + + /// + public byte[] GetCompressedObservation() + { + return null; + } + + /// + public void Update() { } + + /// + public void Reset() { } + + /// + public CompressionSpec GetCompressionSpec() + { + return CompressionSpec.Default(); + } + + /// + public string GetName() + { + return m_SensorName; + } + + /// + public BuiltInSensorType GetBuiltInSensorType() + { + return BuiltInSensorType.ReflectionSensor; + } + } +} diff --git a/com.unity.ml-agents/Runtime/Sensors/Reflection/ReflectionSensorBase.cs.meta b/com.unity.ml-agents/Runtime/Sensors/Reflection/ReflectionSensorBase.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..cef19bb5986347c9feedaa9915894d09948a2942 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Sensors/Reflection/ReflectionSensorBase.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Sensors/Reflection/Vector2ReflectionSensor.cs b/com.unity.ml-agents/Runtime/Sensors/Reflection/Vector2ReflectionSensor.cs new file mode 100644 index 0000000000000000000000000000000000000000..85c6dea8ef825075b817bdadfa4556e292ceec23 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Sensors/Reflection/Vector2ReflectionSensor.cs @@ -0,0 +1,20 @@ +namespace Unity.MLAgents.Sensors.Reflection +{ + /// + /// Sensor that wraps a Vector2 field or property of an object, and returns + /// that as an observation. + /// + internal class Vector2ReflectionSensor : ReflectionSensorBase + { + public Vector2ReflectionSensor(ReflectionSensorInfo reflectionSensorInfo) + : base(reflectionSensorInfo, 2) + { } + + internal override void WriteReflectedField(ObservationWriter writer) + { + var vecVal = (UnityEngine.Vector2)GetReflectedValue(); + writer[0] = vecVal.x; + writer[1] = vecVal.y; + } + } +} diff --git a/com.unity.ml-agents/Runtime/Sensors/Reflection/Vector2ReflectionSensor.cs.meta b/com.unity.ml-agents/Runtime/Sensors/Reflection/Vector2ReflectionSensor.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..2b78c25ffec864f9d3e2dcb7f905da3633b3604e Binary files /dev/null and b/com.unity.ml-agents/Runtime/Sensors/Reflection/Vector2ReflectionSensor.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Sensors/Reflection/Vector3ReflectionSensor.cs b/com.unity.ml-agents/Runtime/Sensors/Reflection/Vector3ReflectionSensor.cs new file mode 100644 index 0000000000000000000000000000000000000000..8fa28b73ee57e3334110132367d87f5cf559d091 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Sensors/Reflection/Vector3ReflectionSensor.cs @@ -0,0 +1,19 @@ +namespace Unity.MLAgents.Sensors.Reflection +{ + /// + /// Sensor that wraps a Vector3 field or property of an object, and returns + /// that as an observation. + /// + internal class Vector3ReflectionSensor : ReflectionSensorBase + { + public Vector3ReflectionSensor(ReflectionSensorInfo reflectionSensorInfo) + : base(reflectionSensorInfo, 3) + { } + + internal override void WriteReflectedField(ObservationWriter writer) + { + var vecVal = (UnityEngine.Vector3)GetReflectedValue(); + writer.Add(vecVal); + } + } +} diff --git a/com.unity.ml-agents/Runtime/Sensors/Reflection/Vector3ReflectionSensor.cs.meta b/com.unity.ml-agents/Runtime/Sensors/Reflection/Vector3ReflectionSensor.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..771b690b07529ac6d55a17c4cf327ce1239f46fe Binary files /dev/null and b/com.unity.ml-agents/Runtime/Sensors/Reflection/Vector3ReflectionSensor.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Sensors/Reflection/Vector4ReflectionSensor.cs b/com.unity.ml-agents/Runtime/Sensors/Reflection/Vector4ReflectionSensor.cs new file mode 100644 index 0000000000000000000000000000000000000000..76b5c39c2767d9538838f591463255d5b1cfa366 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Sensors/Reflection/Vector4ReflectionSensor.cs @@ -0,0 +1,19 @@ +namespace Unity.MLAgents.Sensors.Reflection +{ + /// + /// Sensor that wraps a Vector4 field or property of an object, and returns + /// that as an observation. + /// + internal class Vector4ReflectionSensor : ReflectionSensorBase + { + public Vector4ReflectionSensor(ReflectionSensorInfo reflectionSensorInfo) + : base(reflectionSensorInfo, 4) + { } + + internal override void WriteReflectedField(ObservationWriter writer) + { + var vecVal = (UnityEngine.Vector4)GetReflectedValue(); + writer.Add(vecVal); + } + } +} diff --git a/com.unity.ml-agents/Runtime/Sensors/Reflection/Vector4ReflectionSensor.cs.meta b/com.unity.ml-agents/Runtime/Sensors/Reflection/Vector4ReflectionSensor.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..3d938af6c8e0427f33333605e8c1f6996641054e Binary files /dev/null and b/com.unity.ml-agents/Runtime/Sensors/Reflection/Vector4ReflectionSensor.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Sensors/RenderTextureSensor.cs b/com.unity.ml-agents/Runtime/Sensors/RenderTextureSensor.cs new file mode 100644 index 0000000000000000000000000000000000000000..0980179aa2bd310a94b4984080ba8ec3e19c1763 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Sensors/RenderTextureSensor.cs @@ -0,0 +1,130 @@ +using System; +using UnityEngine; + +namespace Unity.MLAgents.Sensors +{ + /// + /// Sensor class that wraps a [RenderTexture](https://docs.unity3d.com/ScriptReference/RenderTexture.html) instance. + /// + public class RenderTextureSensor : ISensor, IBuiltInSensor, IDisposable + { + RenderTexture m_RenderTexture; + bool m_Grayscale; + string m_Name; + private ObservationSpec m_ObservationSpec; + SensorCompressionType m_CompressionType; + Texture2D m_Texture; + + /// + /// The compression type used by the sensor. + /// + public SensorCompressionType CompressionType + { + get { return m_CompressionType; } + set { m_CompressionType = value; } + } + + + /// + /// Initializes the sensor. + /// + /// The [RenderTexture](https://docs.unity3d.com/ScriptReference/RenderTexture.html) + /// instance to wrap. + /// Whether to convert it to grayscale or not. + /// Name of the sensor. + /// Compression method for the render texture. + // [GameObject]: https://docs.unity3d.com/Manual/GameObjects.html + public RenderTextureSensor( + RenderTexture renderTexture, bool grayscale, string name, SensorCompressionType compressionType) + { + m_RenderTexture = renderTexture; + var width = renderTexture != null ? renderTexture.width : 0; + var height = renderTexture != null ? renderTexture.height : 0; + m_Grayscale = grayscale; + m_Name = name; + m_ObservationSpec = ObservationSpec.Visual(grayscale ? 1 : 3, height, width); + m_CompressionType = compressionType; + m_Texture = new Texture2D(width, height, TextureFormat.RGB24, false); + } + + /// + public string GetName() + { + return m_Name; + } + + /// + public ObservationSpec GetObservationSpec() + { + return m_ObservationSpec; + } + + /// + public byte[] GetCompressedObservation() + { + using (TimerStack.Instance.Scoped("RenderTextureSensor.GetCompressedObservation")) + { + ObservationToTexture(m_RenderTexture, m_Texture); + // TODO support more types here, e.g. JPG + var compressed = m_Texture.EncodeToPNG(); + return compressed; + } + } + + /// + public int Write(ObservationWriter writer) + { + using (TimerStack.Instance.Scoped("RenderTextureSensor.Write")) + { + ObservationToTexture(m_RenderTexture, m_Texture); + var numWritten = writer.WriteTexture(m_Texture, m_Grayscale); + return numWritten; + } + } + + /// + public void Update() { } + + /// + public void Reset() { } + + /// + public CompressionSpec GetCompressionSpec() + { + return new CompressionSpec(m_CompressionType); + } + + /// + public BuiltInSensorType GetBuiltInSensorType() + { + return BuiltInSensorType.RenderTextureSensor; + } + + /// + /// Converts a RenderTexture to a 2D texture. + /// + /// RenderTexture. + /// Texture2D to render to. + public static void ObservationToTexture(RenderTexture obsTexture, Texture2D texture2D) + { + var prevActiveRt = RenderTexture.active; + RenderTexture.active = obsTexture; + + texture2D.ReadPixels(new Rect(0, 0, texture2D.width, texture2D.height), 0, 0); + texture2D.Apply(); + RenderTexture.active = prevActiveRt; + } + + /// + /// Clean up the owned Texture2D. + /// + public void Dispose() + { + if (!ReferenceEquals(null, m_Texture)) + { + Utilities.DestroyTexture(m_Texture); + m_Texture = null; + } + } + } +} diff --git a/com.unity.ml-agents/Runtime/Sensors/RenderTextureSensor.cs.meta b/com.unity.ml-agents/Runtime/Sensors/RenderTextureSensor.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..28a1dff7674843fc3f5b133f4fe211fd12e97985 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Sensors/RenderTextureSensor.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Sensors/RenderTextureSensorComponent.cs b/com.unity.ml-agents/Runtime/Sensors/RenderTextureSensorComponent.cs new file mode 100644 index 0000000000000000000000000000000000000000..8e58617dd3be6a05ed1b41d903bdc241154692ec --- /dev/null +++ b/com.unity.ml-agents/Runtime/Sensors/RenderTextureSensorComponent.cs @@ -0,0 +1,120 @@ +using System; +using UnityEngine; +using UnityEngine.Serialization; + +namespace Unity.MLAgents.Sensors +{ + /// + /// Component that wraps a . + /// + [AddComponentMenu("ML Agents/Render Texture Sensor", (int)MenuGroup.Sensors)] + public class RenderTextureSensorComponent : SensorComponent, IDisposable + { + RenderTextureSensor m_Sensor; + + /// + /// The [RenderTexture](https://docs.unity3d.com/ScriptReference/RenderTexture.html) instance + /// that the associated wraps. + /// + [HideInInspector, SerializeField, FormerlySerializedAs("renderTexture")] + RenderTexture m_RenderTexture; + + /// + /// Stores the [RenderTexture](https://docs.unity3d.com/ScriptReference/RenderTexture.html) + /// associated with this sensor. + /// + public RenderTexture RenderTexture + { + get { return m_RenderTexture; } + set { m_RenderTexture = value; } + } + + [HideInInspector, SerializeField, FormerlySerializedAs("sensorName")] + string m_SensorName = "RenderTextureSensor"; + + /// + /// Name of the generated . + /// Note that changing this at runtime does not affect how the Agent sorts the sensors. + /// + public string SensorName + { + get { return m_SensorName; } + set { m_SensorName = value; } + } + + [HideInInspector, SerializeField, FormerlySerializedAs("grayscale")] + bool m_Grayscale; + + /// + /// Whether the RenderTexture observation should be converted to grayscale or not. + /// Note that changing this after the sensor is created has no effect. + /// + public bool Grayscale + { + get { return m_Grayscale; } + set { m_Grayscale = value; } + } + + [HideInInspector, SerializeField] + [Range(1, 50)] + [Tooltip("Number of frames that will be stacked before being fed to the neural network.")] + int m_ObservationStacks = 1; + + [HideInInspector, SerializeField, FormerlySerializedAs("compression")] + SensorCompressionType m_Compression = SensorCompressionType.PNG; + + /// + /// Compression type for the render texture observation. + /// + public SensorCompressionType CompressionType + { + get { return m_Compression; } + set { m_Compression = value; UpdateSensor(); } + } + + /// + /// Whether to stack previous observations. Using 1 means no previous observations. + /// Note that changing this after the sensor is created has no effect. + /// + public int ObservationStacks + { + get { return m_ObservationStacks; } + set { m_ObservationStacks = value; } + } + + /// + public override ISensor[] CreateSensors() + { + Dispose(); + m_Sensor = new RenderTextureSensor(RenderTexture, Grayscale, SensorName, m_Compression); + if (ObservationStacks != 1) + { + return new ISensor[] { new StackingSensor(m_Sensor, ObservationStacks) }; + } + return new ISensor[] { m_Sensor }; + } + + /// + /// Update fields that are safe to change on the Sensor at runtime. + /// + internal void UpdateSensor() + { + if (m_Sensor != null) + { + m_Sensor.CompressionType = m_Compression; + } + } + + /// + /// Clean up the sensor created by CreateSensors(). + /// + public void Dispose() + { + if (!ReferenceEquals(null, m_Sensor)) + { + m_Sensor.Dispose(); + m_Sensor = null; + } + } + } +} diff --git a/com.unity.ml-agents/Runtime/Sensors/RenderTextureSensorComponent.cs.meta b/com.unity.ml-agents/Runtime/Sensors/RenderTextureSensorComponent.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..542ca3e278b0ce1c7ab2c3ee9fa0bd2b7eafa8df Binary files /dev/null and b/com.unity.ml-agents/Runtime/Sensors/RenderTextureSensorComponent.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Sensors/RigidBodyJointExtractor.cs b/com.unity.ml-agents/Runtime/Sensors/RigidBodyJointExtractor.cs new file mode 100644 index 0000000000000000000000000000000000000000..d984c1f14d8270f3b75439f8af82da834dcf7245 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Sensors/RigidBodyJointExtractor.cs @@ -0,0 +1,87 @@ +using UnityEngine; + +namespace Unity.MLAgents.Sensors +{ + /// + /// Extracts joint and rigidbody information for physics-based sensors. + /// + [UnityEngine.Scripting.APIUpdating.MovedFrom("Unity.MLAgents.Extensions.Sensors")] + public class RigidBodyJointExtractor : IJointExtractor + { + Rigidbody m_Body; + Joint m_Joint; + + /// + /// Initializes a new instance of the class. + /// + /// The Rigidbody to extract joint information from. + public RigidBodyJointExtractor(Rigidbody body) + { + m_Body = body; + m_Joint = m_Body?.GetComponent(); + } + + /// + /// Gets the number of observations for this joint extractor using the provided settings. + /// + /// The physics sensor settings. + /// The number of observations for this joint extractor. + public int NumObservations(PhysicsSensorSettings settings) + { + return NumObservations(m_Body, m_Joint, settings); + } + + /// + /// Gets the number of observations for the specified rigidbody and joint using the provided settings. + /// + /// The Rigidbody to extract from. + /// The Joint to extract from. + /// The physics sensor settings. + /// The number of observations for the specified rigidbody and joint. + public static int NumObservations(Rigidbody body, Joint joint, PhysicsSensorSettings settings) + { + if (body == null || joint == null) + { + return 0; + } + + var numObservations = 0; + if (settings.UseJointForces) + { + // 3 force and 3 torque values + numObservations += 6; + } + + return numObservations; + } + + /// + /// Writes the joint observations to the provided writer using the given settings. + /// + /// The physics sensor settings. + /// The observation writer. + /// The offset in the writer to start writing at. + /// The number of floats written to the writer. + public int Write(PhysicsSensorSettings settings, ObservationWriter writer, int offset) + { + if (m_Body == null || m_Joint == null) + { + return 0; + } + + var currentOffset = offset; + if (settings.UseJointForces) + { + // Take tanh of the forces and torques to ensure they're in [-1, 1] + writer[currentOffset++] = (float)System.Math.Tanh(m_Joint.currentForce.x); + writer[currentOffset++] = (float)System.Math.Tanh(m_Joint.currentForce.y); + writer[currentOffset++] = (float)System.Math.Tanh(m_Joint.currentForce.z); + + writer[currentOffset++] = (float)System.Math.Tanh(m_Joint.currentTorque.x); + writer[currentOffset++] = (float)System.Math.Tanh(m_Joint.currentTorque.y); + writer[currentOffset++] = (float)System.Math.Tanh(m_Joint.currentTorque.z); + } + return currentOffset - offset; + } + } +} diff --git a/com.unity.ml-agents/Runtime/Sensors/RigidBodyJointExtractor.cs.meta b/com.unity.ml-agents/Runtime/Sensors/RigidBodyJointExtractor.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..9d3dc91df92163188f3ffc139b2d581cec5563de Binary files /dev/null and b/com.unity.ml-agents/Runtime/Sensors/RigidBodyJointExtractor.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Sensors/RigidBodyPoseExtractor.cs b/com.unity.ml-agents/Runtime/Sensors/RigidBodyPoseExtractor.cs new file mode 100644 index 0000000000000000000000000000000000000000..15b1927c438aaf3844c9e66ee81c67d8e0c30cf9 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Sensors/RigidBodyPoseExtractor.cs @@ -0,0 +1,208 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace Unity.MLAgents.Sensors +{ + /// + /// Utility class to track a hierarchy of RigidBodies. These are assumed to have a root node, + /// and child nodes are connect to their parents via Joints. + /// + [UnityEngine.Scripting.APIUpdating.MovedFrom("Unity.MLAgents.Extensions.Sensors")] + public class RigidBodyPoseExtractor : PoseExtractor + { + Rigidbody[] m_Bodies; + + /// + /// Optional game object used to determine the root of the poses, separate from the actual Rigidbodies + /// in the hierarchy. For locomotion + /// + GameObject m_VirtualRoot; + + /// + /// Initialize given a root RigidBody. + /// + /// The root Rigidbody. This has no Joints on it (but other Joints may connect to it). + /// Optional GameObject used to find Rigidbodies in the hierarchy. + /// Optional GameObject used to determine the root of the poses, + /// separate from the actual Rigidbodies in the hierarchy. For locomotion tasks, with ragdolls, this provides + /// a stabilized reference frame, which can improve learning. + /// Optional mapping of whether a body's psoe should be enabled or not. + public RigidBodyPoseExtractor(Rigidbody rootBody, GameObject rootGameObject = null, + GameObject virtualRoot = null, Dictionary enableBodyPoses = null) + { + if (rootBody == null) + { + return; + } + + Rigidbody[] rbs; + Joint[] joints; + if (rootGameObject == null) + { + rbs = rootBody.GetComponentsInChildren(); + joints = rootBody.GetComponentsInChildren(); + } + else + { + rbs = rootGameObject.GetComponentsInChildren(); + joints = rootGameObject.GetComponentsInChildren(); + } + + if (rbs == null || rbs.Length == 0) + { + Debug.Log("No rigid bodies found!"); + return; + } + + if (rbs[0] != rootBody) + { + Debug.Log("Expected root body at index 0"); + return; + } + + // Adjust the array if we have a virtual root. + // This will be at index 0, and the "real" root will be parented to it. + if (virtualRoot != null) + { + var extendedRbs = new Rigidbody[rbs.Length + 1]; + for (var i = 0; i < rbs.Length; i++) + { + extendedRbs[i + 1] = rbs[i]; + } + + rbs = extendedRbs; + } + + var bodyToIndex = new Dictionary(rbs.Length); + var parentIndices = new int[rbs.Length]; + parentIndices[0] = -1; + + for (var i = 0; i < rbs.Length; i++) + { + if (rbs[i] != null) + { + bodyToIndex[rbs[i]] = i; + } + } + + foreach (var j in joints) + { + var parent = j.connectedBody; + var child = j.GetComponent(); + + var parentIndex = bodyToIndex[parent]; + var childIndex = bodyToIndex[child]; + parentIndices[childIndex] = parentIndex; + } + + if (virtualRoot != null) + { + // Make sure the original root treats the virtual root as its parent. + parentIndices[1] = 0; + m_VirtualRoot = virtualRoot; + } + + m_Bodies = rbs; + Setup(parentIndices); + + // By default, ignore the root + SetPoseEnabled(0, false); + + if (enableBodyPoses != null) + { + foreach (var pair in enableBodyPoses) + { + var rb = pair.Key; + if (bodyToIndex.TryGetValue(rb, out var index)) + { + SetPoseEnabled(index, pair.Value); + } + } + } + } + + /// + protected internal override Vector3 GetLinearVelocityAt(int index) + { + if (index == 0 && m_VirtualRoot != null) + { + // No velocity on the virtual root + return Vector3.zero; + } + return m_Bodies[index].linearVelocity; + } + + /// + protected internal override Pose GetPoseAt(int index) + { + if (index == 0 && m_VirtualRoot != null) + { + // Use the GameObject's world transform + return new Pose + { + rotation = m_VirtualRoot.transform.rotation, + position = m_VirtualRoot.transform.position + }; + } + + var body = m_Bodies[index]; + return new Pose { rotation = body.rotation, position = body.position }; + } + + /// + protected internal override Object GetObjectAt(int index) + { + if (index == 0 && m_VirtualRoot != null) + { + return m_VirtualRoot; + } + return m_Bodies[index]; + } + + internal Rigidbody[] Bodies => m_Bodies; + + /// + /// Get a dictionary indicating which Rigidbodies' poses are enabled or disabled. + /// + /// `Dictionary` indicating which Rigidbodies' poses are enabled or disabled. + internal Dictionary GetBodyPosesEnabled() + { + var bodyPosesEnabled = new Dictionary(m_Bodies.Length); + for (var i = 0; i < m_Bodies.Length; i++) + { + var rb = m_Bodies[i]; + if (rb == null) + { + continue; // skip virtual root + } + + bodyPosesEnabled[rb] = IsPoseEnabled(i); + } + + return bodyPosesEnabled; + } + + internal IEnumerable GetEnabledRigidbodies() + { + if (m_Bodies == null) + { + yield break; + } + + for (var i = 0; i < m_Bodies.Length; i++) + { + var rb = m_Bodies[i]; + if (rb == null) + { + // Ignore a virtual root. + continue; + } + + if (IsPoseEnabled(i)) + { + yield return rb; + } + } + } + } +} diff --git a/com.unity.ml-agents/Runtime/Sensors/RigidBodyPoseExtractor.cs.meta b/com.unity.ml-agents/Runtime/Sensors/RigidBodyPoseExtractor.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..8418e2f14696d6937529a7a4e298fa39ec48999e Binary files /dev/null and b/com.unity.ml-agents/Runtime/Sensors/RigidBodyPoseExtractor.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Sensors/RigidBodySensorComponent.cs b/com.unity.ml-agents/Runtime/Sensors/RigidBodySensorComponent.cs new file mode 100644 index 0000000000000000000000000000000000000000..75719662455acf7da3875270ae826fa47922773c --- /dev/null +++ b/com.unity.ml-agents/Runtime/Sensors/RigidBodySensorComponent.cs @@ -0,0 +1,112 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace Unity.MLAgents.Sensors +{ + /// + /// Editor component that creates a PhysicsBodySensor for the Agent. + /// + [UnityEngine.Scripting.APIUpdating.MovedFrom("Unity.MLAgents.Extensions.Sensors")] + public class RigidBodySensorComponent : SensorComponent + { + /// + /// The root Rigidbody of the system. + /// + public Rigidbody RootBody; + + /// + /// Optional GameObject used to determine the root of the poses. + /// + public GameObject VirtualRoot; + + /// + /// Settings defining what types of observations will be generated. + /// + [SerializeField] + public PhysicsSensorSettings Settings = PhysicsSensorSettings.Default(); + + /// + /// Optional sensor name. This must be unique for each Agent. + /// + [SerializeField] + public string sensorName; + + [HideInInspector] + RigidBodyPoseExtractor m_PoseExtractor; + + /// + /// Creates a PhysicsBodySensor. + /// + /// Corresponding sensors. + public override ISensor[] CreateSensors() + { + var _sensorName = string.IsNullOrEmpty(sensorName) ? $"PhysicsBodySensor:{RootBody?.name}" : sensorName; + return new ISensor[] { new PhysicsBodySensor(GetPoseExtractor(), Settings, _sensorName) }; + } + + /// + /// Get the DisplayNodes of the hierarchy. + /// + /// The `DisplayNodes` of the hierarchy. + internal IList GetDisplayNodes() + { + return GetPoseExtractor().GetDisplayNodes(); + } + + /// + /// Lazy construction of the PoseExtractor. + /// + /// Corresponding `RigidBodyPoseExtractor` + RigidBodyPoseExtractor GetPoseExtractor() + { + if (m_PoseExtractor == null) + { + ResetPoseExtractor(); + } + + return m_PoseExtractor; + } + + /// + /// Reset the pose extractor, trying to keep the enabled state of the corresponding poses the same. + /// + internal void ResetPoseExtractor() + { + // Get the current enabled state of each body, so that we can reinitialize with them. + Dictionary bodyPosesEnabled = null; + if (m_PoseExtractor != null) + { + bodyPosesEnabled = m_PoseExtractor.GetBodyPosesEnabled(); + } + m_PoseExtractor = new RigidBodyPoseExtractor(RootBody, gameObject, VirtualRoot, bodyPosesEnabled); + } + + /// + /// Toggle the pose at the given index. + /// + /// + /// + internal void SetPoseEnabled(int index, bool enabled) + { + GetPoseExtractor().SetPoseEnabled(index, enabled); + } + + internal bool IsTrivial() + { + if (ReferenceEquals(RootBody, null)) + { + // It *is* trivial, but this will happen when the sensor is being set up, so don't warn then. + return false; + } + var joints = RootBody.GetComponentsInChildren(); + if (joints.Length == 0) + { + if (ReferenceEquals(VirtualRoot, null) || ReferenceEquals(VirtualRoot, RootBody.gameObject)) + { + return true; + } + } + return false; + } + } +} diff --git a/com.unity.ml-agents/Runtime/Sensors/RigidBodySensorComponent.cs.meta b/com.unity.ml-agents/Runtime/Sensors/RigidBodySensorComponent.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..59ba148382c057db2ec0d4be26963d08ae80290c Binary files /dev/null and b/com.unity.ml-agents/Runtime/Sensors/RigidBodySensorComponent.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Sensors/SensorComponent.cs b/com.unity.ml-agents/Runtime/Sensors/SensorComponent.cs new file mode 100644 index 0000000000000000000000000000000000000000..4ddcaabb74a84057600aa110871555548f34375f --- /dev/null +++ b/com.unity.ml-agents/Runtime/Sensors/SensorComponent.cs @@ -0,0 +1,17 @@ +using UnityEngine; + +namespace Unity.MLAgents.Sensors +{ + /// + /// Editor components for creating Sensors. Generally an ISensor implementation should have a + /// corresponding SensorComponent to create it. + /// + public abstract class SensorComponent : MonoBehaviour + { + /// + /// Create the ISensors. This is called by the Agent when it is initialized. + /// + /// Created ISensor objects. + public abstract ISensor[] CreateSensors(); + } +} diff --git a/com.unity.ml-agents/Runtime/Sensors/SensorComponent.cs.meta b/com.unity.ml-agents/Runtime/Sensors/SensorComponent.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..5576281e12aadd57fbf3b977e76006b86f56460d Binary files /dev/null and b/com.unity.ml-agents/Runtime/Sensors/SensorComponent.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Sensors/SensorShapeValidator.cs b/com.unity.ml-agents/Runtime/Sensors/SensorShapeValidator.cs new file mode 100644 index 0000000000000000000000000000000000000000..879e8924c1a4d96d19c2fbfa3efeb11da2f92587 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Sensors/SensorShapeValidator.cs @@ -0,0 +1,58 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace Unity.MLAgents.Sensors +{ + /// + /// Check that List Sensors are the same shape as the previous ones. + /// + public class SensorShapeValidator + { + List m_SensorShapes; + + /// + /// Check that the List Sensors are the same shape as the previous ones. + /// If this is the first List of Sensors being checked, its Sensor sizes will be saved. + /// + /// List of Sensors to validate + public void ValidateSensors(List sensors) + { + if (m_SensorShapes == null) + { + m_SensorShapes = new List(sensors.Count); + // First agent, save the sensor sizes + foreach (var sensor in sensors) + { + m_SensorShapes.Add(sensor.GetObservationSpec()); + } + } + else + { + // Check for compatibility with the other Agents' Sensors + if (m_SensorShapes.Count != sensors.Count) + { + Debug.AssertFormat( + m_SensorShapes.Count == sensors.Count, + "Number of Sensors must match. {0} != {1}", + m_SensorShapes.Count, + sensors.Count + ); + } + for (var i = 0; i < Mathf.Min(m_SensorShapes.Count, sensors.Count); i++) + { + var cachedSpec = m_SensorShapes[i]; + var sensorSpec = sensors[i].GetObservationSpec(); + if (cachedSpec.Shape != sensorSpec.Shape) + { + Debug.AssertFormat( + cachedSpec.Shape == sensorSpec.Shape, + "Sensor shapes must match. {0} != {1}", + cachedSpec.Shape, + sensorSpec.Shape + ); + } + } + } + } + } +} diff --git a/com.unity.ml-agents/Runtime/Sensors/SensorShapeValidator.cs.meta b/com.unity.ml-agents/Runtime/Sensors/SensorShapeValidator.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..6ce44665cc61ac44dd1bf53758f0f3058e220852 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Sensors/SensorShapeValidator.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Sensors/StackingSensor.cs b/com.unity.ml-agents/Runtime/Sensors/StackingSensor.cs new file mode 100644 index 0000000000000000000000000000000000000000..f65cb0af280b78500e14ad8d0862fc2a57de9550 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Sensors/StackingSensor.cs @@ -0,0 +1,329 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using UnityEngine; +using Unity.InferenceEngine; +using Unity.MLAgents.Inference; + +namespace Unity.MLAgents.Sensors +{ + /// + /// Sensor that wraps around another Sensor to provide temporal stacking. + /// Conceptually, consecutive observations are stored left-to-right, which is how they're output + /// For example, 4 stacked sets of observations would be output like + /// | t = now - 3 | t = now -2 | t = now - 1 | t = now | + /// Internally, a circular buffer of arrays is used. The m_CurrentIndex represents the most recent observation. + /// Currently, observations are stacked on the last dimension. + /// + public class StackingSensor : ISensor, IBuiltInSensor + { + /// + /// The wrapped sensor. + /// + ISensor m_WrappedSensor; + + /// + /// Number of stacks to save + /// + int m_NumStackedObservations; + int m_UnstackedObservationSize; + + string m_Name; + private ObservationSpec m_ObservationSpec; + private ObservationSpec m_WrappedSpec; + + /// + /// Buffer of previous observations + /// + float[][] m_StackedObservations; + + byte[][] m_StackedCompressedObservations; + + int m_CurrentIndex; + ObservationWriter m_LocalWriter = new ObservationWriter(); + + byte[] m_EmptyCompressedObservation; + int[] m_CompressionMapping; + TensorShape m_TensorShape; + int[,,] m_TensorIndex; + + /// + /// Initializes the sensor. + /// + /// The wrapped sensor. + /// Number of stacked observations to keep. + public StackingSensor(ISensor wrapped, int numStackedObservations) + { + // TODO ensure numStackedObservations > 1 + m_WrappedSensor = wrapped; + m_NumStackedObservations = numStackedObservations; + + m_Name = $"StackingSensor_size{numStackedObservations}_{wrapped.GetName()}"; + + m_WrappedSpec = wrapped.GetObservationSpec(); + + m_UnstackedObservationSize = wrapped.ObservationSize(); + + // Set up the cached observation spec for the StackingSensor + var newShape = m_WrappedSpec.Shape; + // TODO support arbitrary stacking dimension + newShape[newShape.Length < 3 ? newShape.Length - 1 : newShape.Length - 3] *= numStackedObservations; + m_ObservationSpec = new ObservationSpec( + newShape, m_WrappedSpec.DimensionProperties, m_WrappedSpec.ObservationType + ); + + // Initialize uncompressed buffer anyway in case python trainer does not + // support the compression mapping and has to fall back to uncompressed obs. + m_StackedObservations = new float[numStackedObservations][]; + for (var i = 0; i < numStackedObservations; i++) + { + m_StackedObservations[i] = new float[m_UnstackedObservationSize]; + } + + if (m_WrappedSensor.GetCompressionSpec().SensorCompressionType != SensorCompressionType.None) + { + m_StackedCompressedObservations = new byte[numStackedObservations][]; + + var numEmptyPNGs = (m_WrappedSpec.Shape[0] + 2) / 3; + // Generate Single Empty PNG + byte[] singleEmptyPNG = CreateEmptyPNG(); + List emptyCompressedObservationList = new List(); + // Combine Multiple Empty PNGs for channels more than 3 + for (int i = 0; i < numEmptyPNGs; i++) + { + emptyCompressedObservationList.AddRange(singleEmptyPNG); + } + m_EmptyCompressedObservation = emptyCompressedObservationList.ToArray(); + + for (var i = 0; i < numStackedObservations; i++) + { + m_StackedCompressedObservations[i] = m_EmptyCompressedObservation; + } + m_CompressionMapping = ConstructStackedCompressedChannelMapping(wrapped); + } + + if (m_WrappedSpec.Rank != 1) + { + var wrappedShape = m_WrappedSpec.Shape; + m_TensorShape = new TensorShape(0, wrappedShape[0], wrappedShape[1], wrappedShape[2]); + } + + if (m_WrappedSpec.Rank == 3) + { + m_TensorIndex = new int[m_WrappedSpec.Shape[0], m_WrappedSpec.Shape[1], m_WrappedSpec.Shape[2]]; + + for (var h = 0; h < m_WrappedSpec.Shape[1]; h++) + { + for (var w = 0; w < m_WrappedSpec.Shape[2]; w++) + { + for (var c = 0; c < m_WrappedSpec.Shape[0]; c++) + { + m_TensorIndex[c, h, w] = m_TensorShape.Index(0, c, h, w); + } + } + } + } + } + + /// + public int Write(ObservationWriter writer) + { + // First, call the wrapped sensor's write method. Make sure to use our own writer, not the passed one. + m_LocalWriter.SetTarget(m_StackedObservations[m_CurrentIndex], m_WrappedSpec, 0); + m_WrappedSensor.Write(m_LocalWriter); + + // Now write the saved observations (oldest first) + var numWritten = 0; + if (m_WrappedSpec.Rank == 1) + { + for (var i = 0; i < m_NumStackedObservations; i++) + { + var obsIndex = (m_CurrentIndex + 1 + i) % m_NumStackedObservations; + writer.AddList(m_StackedObservations[obsIndex], numWritten); + numWritten += m_UnstackedObservationSize; + } + } + else + { + for (var i = 0; i < m_NumStackedObservations; i++) + { + var obsIndex = (m_CurrentIndex + 1 + i) % m_NumStackedObservations; + for (var h = 0; h < m_WrappedSpec.Shape[1]; h++) + { + for (var w = 0; w < m_WrappedSpec.Shape[2]; w++) + { + for (var c = 0; c < m_WrappedSpec.Shape[0]; c++) + { + writer[i * m_WrappedSpec.Shape[0] + c, h, w] = m_StackedObservations[obsIndex][m_TensorIndex[c, h, w]]; + } + } + } + } + numWritten = m_WrappedSpec.Shape[0] * m_WrappedSpec.Shape[1] * m_WrappedSpec.Shape[2] * m_NumStackedObservations; + } + + return numWritten; + } + + /// + /// Updates the index of the "current" buffer. + /// + public void Update() + { + m_WrappedSensor.Update(); + m_CurrentIndex = (m_CurrentIndex + 1) % m_NumStackedObservations; + } + + /// + public void Reset() + { + m_WrappedSensor.Reset(); + // Zero out the buffer. + for (var i = 0; i < m_NumStackedObservations; i++) + { + Array.Clear(m_StackedObservations[i], 0, m_StackedObservations[i].Length); + } + if (m_WrappedSensor.GetCompressionSpec().SensorCompressionType != SensorCompressionType.None) + { + for (var i = 0; i < m_NumStackedObservations; i++) + { + m_StackedCompressedObservations[i] = m_EmptyCompressedObservation; + } + } + } + + /// + public ObservationSpec GetObservationSpec() + { + return m_ObservationSpec; + } + + /// + public string GetName() + { + return m_Name; + } + + /// + public byte[] GetCompressedObservation() + { + var compressed = m_WrappedSensor.GetCompressedObservation(); + m_StackedCompressedObservations[m_CurrentIndex] = compressed; + + int bytesLength = 0; + foreach (byte[] compressedObs in m_StackedCompressedObservations) + { + bytesLength += compressedObs.Length; + } + + byte[] outputBytes = new byte[bytesLength]; + int offset = 0; + for (var i = 0; i < m_NumStackedObservations; i++) + { + var obsIndex = (m_CurrentIndex + 1 + i) % m_NumStackedObservations; + Buffer.BlockCopy(m_StackedCompressedObservations[obsIndex], + 0, outputBytes, offset, m_StackedCompressedObservations[obsIndex].Length); + offset += m_StackedCompressedObservations[obsIndex].Length; + } + + return outputBytes; + } + + /// + public CompressionSpec GetCompressionSpec() + { + var wrappedSpec = m_WrappedSensor.GetCompressionSpec(); + return new CompressionSpec(wrappedSpec.SensorCompressionType, m_CompressionMapping); + } + + /// + /// Create Empty PNG for initializing the buffer for stacking. + /// + internal byte[] CreateEmptyPNG() + { + var shape = m_WrappedSpec.Shape; + int height = shape[1]; + int width = shape[2]; + var texture2D = new Texture2D(width, height, TextureFormat.RGB24, false); + Color32[] resetColorArray = texture2D.GetPixels32(); + Color32 black = new Color32(0, 0, 0, 0); + for (int i = 0; i < resetColorArray.Length; i++) + { + resetColorArray[i] = black; + } + texture2D.SetPixels32(resetColorArray); + texture2D.Apply(); + return texture2D.EncodeToPNG(); + } + + /// + /// Construct stacked CompressedChannelMapping. + /// + internal int[] ConstructStackedCompressedChannelMapping(ISensor wrappedSenesor) + { + // Get CompressedChannelMapping of the wrapped sensor. If the + // wrapped sensor doesn't have one, use default mapping. + // Default mapping: {0, 0, 0} for grayscale, identity mapping {1, 2, ..., n} otherwise. + int[] wrappedMapping = null; + int wrappedNumChannel = m_WrappedSpec.Shape[0]; + + wrappedMapping = wrappedSenesor.GetCompressionSpec().CompressedChannelMapping; + if (wrappedMapping == null) + { + if (wrappedNumChannel == 1) + { + wrappedMapping = new[] { 0, 0, 0 }; + } + else + { + wrappedMapping = Enumerable.Range(0, wrappedNumChannel).ToArray(); + } + } + + // Construct stacked mapping using the mapping of wrapped sensor. + // First pad the wrapped mapping to multiple of 3, then repeat + // and add offset to each copy to form the stacked mapping. + int paddedMapLength = (wrappedMapping.Length + 2) / 3 * 3; + var compressionMapping = new int[paddedMapLength * m_NumStackedObservations]; + for (var i = 0; i < m_NumStackedObservations; i++) + { + var offset = wrappedNumChannel * i; + for (var j = 0; j < paddedMapLength; j++) + { + if (j < wrappedMapping.Length) + { + compressionMapping[j + paddedMapLength * i] = wrappedMapping[j] >= 0 ? wrappedMapping[j] + offset : -1; + } + else + { + compressionMapping[j + paddedMapLength * i] = -1; + } + } + } + return compressionMapping; + } + + /// + public BuiltInSensorType GetBuiltInSensorType() + { + IBuiltInSensor wrappedBuiltInSensor = m_WrappedSensor as IBuiltInSensor; + return wrappedBuiltInSensor?.GetBuiltInSensorType() ?? BuiltInSensorType.Unknown; + } + + /// + /// Returns the stacked observations as a read-only collection. + /// + /// The stacked observations as a read-only collection. + internal ReadOnlyCollection GetStackedObservations() + { + List observations = new List(); + for (var i = 0; i < m_NumStackedObservations; i++) + { + var obsIndex = (m_CurrentIndex + 1 + i) % m_NumStackedObservations; + observations.AddRange(m_StackedObservations[obsIndex].ToList()); + } + return observations.AsReadOnly(); + } + } +} diff --git a/com.unity.ml-agents/Runtime/Sensors/StackingSensor.cs.meta b/com.unity.ml-agents/Runtime/Sensors/StackingSensor.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..f0289542ff49c1974b70936e993fe726308c6cc3 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Sensors/StackingSensor.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Sensors/VectorSensor.cs b/com.unity.ml-agents/Runtime/Sensors/VectorSensor.cs new file mode 100644 index 0000000000000000000000000000000000000000..3f6a79f4bafd66a8264f502c368a2f78444d3811 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Sensors/VectorSensor.cs @@ -0,0 +1,218 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using UnityEngine; + +namespace Unity.MLAgents.Sensors +{ + /// + /// A sensor implementation for vector observations. + /// + public class VectorSensor : ISensor, IBuiltInSensor + { + // TODO use float[] instead + // TODO allow setting float[] + List m_Observations; + ObservationSpec m_ObservationSpec; + string m_Name; + + /// + /// Initializes the sensor. + /// + /// Number of vector observations. + /// Name of the sensor. + /// Observation type + public VectorSensor(int observationSize, string name = null, ObservationType observationType = ObservationType.Default) + { + if (string.IsNullOrEmpty(name)) + { + name = $"VectorSensor_size{observationSize}"; + if (observationType != ObservationType.Default) + { + name += $"_{observationType.ToString()}"; + } + } + + m_Observations = new List(observationSize); + m_Name = name; + m_ObservationSpec = ObservationSpec.Vector(observationSize, observationType); + } + + /// + public int Write(ObservationWriter writer) + { + var expectedObservations = m_ObservationSpec.Shape[0]; + if (m_Observations.Count > expectedObservations) + { + // Too many observations, truncate + Debug.LogWarningFormat( + "More observations ({0}) made than vector observation size ({1}). The observations will be truncated.", + m_Observations.Count, expectedObservations + ); + m_Observations.RemoveRange(expectedObservations, m_Observations.Count - expectedObservations); + } + else if (m_Observations.Count < expectedObservations) + { + // Not enough observations; pad with zeros. + Debug.LogWarningFormat( + "Fewer observations ({0}) made than vector observation size ({1}). The observations will be padded.", + m_Observations.Count, expectedObservations + ); + for (int i = m_Observations.Count; i < expectedObservations; i++) + { + m_Observations.Add(0); + } + } + writer.AddList(m_Observations); + return expectedObservations; + } + + /// + /// Returns a read-only view of the observations that added. + /// + /// A read-only view of the observations list. + internal ReadOnlyCollection GetObservations() + { + return m_Observations.AsReadOnly(); + } + + /// + public void Update() + { + Clear(); + } + + /// + public void Reset() + { + Clear(); + } + + /// + public ObservationSpec GetObservationSpec() + { + return m_ObservationSpec; + } + + /// + public string GetName() + { + return m_Name; + } + + /// + public virtual byte[] GetCompressedObservation() + { + return null; + } + + /// + public CompressionSpec GetCompressionSpec() + { + return CompressionSpec.Default(); + } + + /// + public BuiltInSensorType GetBuiltInSensorType() + { + return BuiltInSensorType.VectorSensor; + } + + void Clear() + { + m_Observations.Clear(); + } + + void AddFloatObs(float obs) + { + Utilities.DebugCheckNanAndInfinity(obs, nameof(obs), nameof(AddFloatObs)); + m_Observations.Add(obs); + } + + // Compatibility methods with Agent observation. These should be removed eventually. + + /// + /// Adds a float observation to the vector observations of the agent. + /// + /// Observation. + public void AddObservation(float observation) + { + AddFloatObs(observation); + } + + /// + /// Adds an integer observation to the vector observations of the agent. + /// + /// Observation. + public void AddObservation(int observation) + { + AddFloatObs(observation); + } + + /// + /// Adds an Vector3 observation to the vector observations of the agent. + /// + /// Observation. + public void AddObservation(Vector3 observation) + { + AddFloatObs(observation.x); + AddFloatObs(observation.y); + AddFloatObs(observation.z); + } + + /// + /// Adds an Vector2 observation to the vector observations of the agent. + /// + /// Observation. + public void AddObservation(Vector2 observation) + { + AddFloatObs(observation.x); + AddFloatObs(observation.y); + } + + /// + /// Adds a list or array of float observations to the vector observations of the agent. + /// + /// Observation. + public void AddObservation(IList observation) + { + for (var i = 0; i < observation.Count; i++) + { + AddFloatObs(observation[i]); + } + } + + /// + /// Adds a quaternion observation to the vector observations of the agent. + /// + /// Observation. + public void AddObservation(Quaternion observation) + { + AddFloatObs(observation.x); + AddFloatObs(observation.y); + AddFloatObs(observation.z); + AddFloatObs(observation.w); + } + + /// + /// Adds a boolean observation to the vector observation of the agent. + /// + /// Observation. + public void AddObservation(bool observation) + { + AddFloatObs(observation ? 1f : 0f); + } + + /// + /// Adds a one-hot encoding observation. + /// + /// The index of this observation. + /// The upper limit on the value observation can take (exclusive). + public void AddOneHotObservation(int observation, int range) + { + for (var i = 0; i < range; i++) + { + AddFloatObs(i == observation ? 1.0f : 0.0f); + } + } + } +} diff --git a/com.unity.ml-agents/Runtime/Sensors/VectorSensor.cs.meta b/com.unity.ml-agents/Runtime/Sensors/VectorSensor.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..277ef0d59e2ccb8290f16000ac73c99a2d82ecff Binary files /dev/null and b/com.unity.ml-agents/Runtime/Sensors/VectorSensor.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Sensors/VectorSensorComponent.cs b/com.unity.ml-agents/Runtime/Sensors/VectorSensorComponent.cs new file mode 100644 index 0000000000000000000000000000000000000000..71e36d9b5bc3bf871e6e765c9f7ac82b188b291c --- /dev/null +++ b/com.unity.ml-agents/Runtime/Sensors/VectorSensorComponent.cs @@ -0,0 +1,87 @@ +using UnityEngine; + +namespace Unity.MLAgents.Sensors +{ + /// + /// A SensorComponent that creates a . + /// + [AddComponentMenu("ML Agents/Vector Sensor", (int)MenuGroup.Sensors)] + public class VectorSensorComponent : SensorComponent + { + /// + /// Name of the generated object. + /// Note that changing this at runtime does not affect how the Agent sorts the sensors. + /// + public string SensorName + { + get { return m_SensorName; } + set { m_SensorName = value; } + } + [HideInInspector, SerializeField] + private string m_SensorName = "VectorSensor"; + + /// + /// The number of float observations in the VectorSensor + /// + public int ObservationSize + { + get { return m_ObservationSize; } + set { m_ObservationSize = value; } + } + + [HideInInspector, SerializeField] + int m_ObservationSize; + + [HideInInspector, SerializeField] + ObservationType m_ObservationType; + + VectorSensor m_Sensor; + + /// + /// The type of the observation. + /// + public ObservationType ObservationType + { + get { return m_ObservationType; } + set { m_ObservationType = value; } + } + + [HideInInspector, SerializeField] + [Range(1, 50)] + [Tooltip("Number of camera frames that will be stacked before being fed to the neural network.")] + int m_ObservationStacks = 1; + + /// + /// Whether to stack previous observations. Using 1 means no previous observations. + /// Note that changing this after the sensor is created has no effect. + /// + public int ObservationStacks + { + get { return m_ObservationStacks; } + set { m_ObservationStacks = value; } + } + + /// + /// Creates a VectorSensor. + /// + /// `ISensor` array. + public override ISensor[] CreateSensors() + { + m_Sensor = new VectorSensor(m_ObservationSize, m_SensorName, m_ObservationType); + if (ObservationStacks != 1) + { + return new ISensor[] { new StackingSensor(m_Sensor, ObservationStacks) }; + } + return new ISensor[] { m_Sensor }; + } + + /// + /// Returns the underlying VectorSensor + /// + /// Underlying `VectorSensor`. + public VectorSensor GetSensor() + { + return m_Sensor; + } + } +} diff --git a/com.unity.ml-agents/Runtime/Sensors/VectorSensorComponent.cs.meta b/com.unity.ml-agents/Runtime/Sensors/VectorSensorComponent.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..c867a60f2b2b1375f17c949a6eef501f530bb7d6 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Sensors/VectorSensorComponent.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/SideChannels.meta b/com.unity.ml-agents/Runtime/SideChannels.meta new file mode 100644 index 0000000000000000000000000000000000000000..6bff982a90afbb98a3d35f8424c8d7e17254984b Binary files /dev/null and b/com.unity.ml-agents/Runtime/SideChannels.meta differ diff --git a/com.unity.ml-agents/Runtime/SideChannels/EngineConfigurationChannel.cs b/com.unity.ml-agents/Runtime/SideChannels/EngineConfigurationChannel.cs new file mode 100644 index 0000000000000000000000000000000000000000..246ef50074e4da650639662d305e5409023e59ef --- /dev/null +++ b/com.unity.ml-agents/Runtime/SideChannels/EngineConfigurationChannel.cs @@ -0,0 +1,75 @@ +using System; +using UnityEngine; + +namespace Unity.MLAgents.SideChannels +{ + /// + /// Side channel that supports modifying attributes specific to the Unity Engine. + /// + internal class EngineConfigurationChannel : SideChannel + { + internal enum ConfigurationType : int + { + ScreenResolution = 0, + QualityLevel = 1, + TimeScale = 2, + TargetFrameRate = 3, + CaptureFrameRate = 4 + } + + const string k_EngineConfigId = "e951342c-4f7e-11ea-b238-784f4387d1f7"; + + /// + /// Initializes the side channel. The constructor is internal because only one instance is + /// supported at a time, and is created by the Academy. + /// + internal EngineConfigurationChannel() + { + ChannelId = new Guid(k_EngineConfigId); + } + + /// + protected override void OnMessageReceived(IncomingMessage msg) + { + 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(); + + // There's an upper limit for the timeScale in the editor (but not in the player) + // Always ensure that timeScale >= 1 also, +#if UNITY_EDITOR + const float maxTimeScale = 100f; +#else + const float maxTimeScale = float.PositiveInfinity; +#endif + timeScale = Mathf.Clamp(timeScale, 1, maxTimeScale); + 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; + } + } + } +} diff --git a/com.unity.ml-agents/Runtime/SideChannels/EngineConfigurationChannel.cs.meta b/com.unity.ml-agents/Runtime/SideChannels/EngineConfigurationChannel.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..8f6335e9b05bddcf8dc7b9d80f0796c820dc6fbf Binary files /dev/null and b/com.unity.ml-agents/Runtime/SideChannels/EngineConfigurationChannel.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/SideChannels/EnvironmentParametersChannel.cs b/com.unity.ml-agents/Runtime/SideChannels/EnvironmentParametersChannel.cs new file mode 100644 index 0000000000000000000000000000000000000000..0395ca00c1eda7ccdee1e37768f636440ffb0888 --- /dev/null +++ b/com.unity.ml-agents/Runtime/SideChannels/EnvironmentParametersChannel.cs @@ -0,0 +1,142 @@ +using System.Collections.Generic; +using System; +using UnityEngine; + +namespace Unity.MLAgents.SideChannels +{ + /// + /// Lists the different data types supported. + /// + internal enum EnvironmentDataTypes + { + Float = 0, + Sampler = 1 + } + + /// + /// The types of distributions from which to sample reset parameters. + /// + internal enum SamplerType + { + /// + /// Samples a reset parameter from a uniform distribution. + /// + Uniform = 0, + + /// + /// Samples a reset parameter from a Gaussian distribution. + /// + Gaussian = 1, + + /// + /// Samples a reset parameter from a MultiRangeUniform distribution. + /// + MultiRangeUniform = 2 + } + + /// + /// A side channel that manages the environment parameter values from Python. Currently + /// limited to parameters of type float. + /// + internal class EnvironmentParametersChannel : SideChannel + { + Dictionary> m_Parameters = new Dictionary>(); + Dictionary> m_RegisteredActions = + new Dictionary>(); + + const string k_EnvParamsId = "534c891e-810f-11ea-a9d0-822485860400"; + + /// + /// Initializes the side channel. The constructor is internal because only one instance is + /// supported at a time, and is created by the Academy. + /// + internal EnvironmentParametersChannel() + { + ChannelId = new Guid(k_EnvParamsId); + } + + /// + 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 action; + m_RegisteredActions.TryGetValue(key, out action); + action?.Invoke(value); + } + else if ((int)EnvironmentDataTypes.Sampler == type) + { + int seed = msg.ReadInt32(); + int samplerType = msg.ReadInt32(); + Func sampler = () => 0.0f; + if ((int)SamplerType.Uniform == samplerType) + { + float min = msg.ReadFloat32(); + float max = msg.ReadFloat32(); + sampler = SamplerFactory.CreateUniformSampler(min, max, seed); + } + else if ((int)SamplerType.Gaussian == samplerType) + { + float mean = msg.ReadFloat32(); + float stddev = msg.ReadFloat32(); + + sampler = SamplerFactory.CreateGaussianSampler(mean, stddev, seed); + } + else if ((int)SamplerType.MultiRangeUniform == samplerType) + { + IList intervals = msg.ReadFloatList(); + sampler = SamplerFactory.CreateMultiRangeUniformSampler(intervals, seed); + } + else + { + Debug.LogWarning("EnvironmentParametersChannel received an unknown data type."); + } + m_Parameters[key] = sampler; + } + else + { + Debug.LogWarning("EnvironmentParametersChannel received an unknown data type."); + } + } + + /// + /// Returns the parameter value associated with the provided key. Returns the default + /// value if one doesn't exist. + /// + /// Parameter key. + /// Default value to return. + /// The parameter value associated with the provided key. + public float GetWithDefault(string key, float defaultValue) + { + Func valueOut; + bool hasKey = m_Parameters.TryGetValue(key, out valueOut); + return hasKey ? valueOut.Invoke() : defaultValue; + } + + /// + /// Registers a callback for the associated parameter key. Will overwrite any existing + /// actions for this parameter key. + /// + /// The parameter key. + /// The callback. + public void RegisterCallback(string key, Action action) + { + m_RegisteredActions[key] = action; + } + + /// + /// Returns all parameter keys that have a registered value. + /// + /// All parameter keys that have a registered value. + public IList ListParameters() + { + return new List(m_Parameters.Keys); + } + } +} diff --git a/com.unity.ml-agents/Runtime/SideChannels/EnvironmentParametersChannel.cs.meta b/com.unity.ml-agents/Runtime/SideChannels/EnvironmentParametersChannel.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..f118b1f99fe6d8e9d5dafebae18d653b84af2067 Binary files /dev/null and b/com.unity.ml-agents/Runtime/SideChannels/EnvironmentParametersChannel.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/SideChannels/FloatPropertiesChannel.cs b/com.unity.ml-agents/Runtime/SideChannels/FloatPropertiesChannel.cs new file mode 100644 index 0000000000000000000000000000000000000000..d93728b0eb1eb568e668b071e9d1cd9ee62de4db --- /dev/null +++ b/com.unity.ml-agents/Runtime/SideChannels/FloatPropertiesChannel.cs @@ -0,0 +1,97 @@ +using System.Collections.Generic; +using System; + +namespace Unity.MLAgents.SideChannels +{ + /// + /// Side channel that is comprised of a collection of float variables. + /// + public class FloatPropertiesChannel : SideChannel + { + Dictionary m_FloatProperties = new Dictionary(); + Dictionary> m_RegisteredActions = new Dictionary>(); + const string k_FloatPropertiesDefaultId = "60ccf7d0-4f7e-11ea-b238-784f4387d1f7"; + + /// + /// Initializes the side channel with the provided channel ID. + /// + /// ID for the side channel. + public FloatPropertiesChannel(Guid channelId = default(Guid)) + { + if (channelId == default(Guid)) + { + ChannelId = new Guid(k_FloatPropertiesDefaultId); + } + else + { + ChannelId = channelId; + } + } + + /// + protected override void OnMessageReceived(IncomingMessage msg) + { + var key = msg.ReadString(); + var value = msg.ReadFloat32(); + + m_FloatProperties[key] = value; + + Action action; + m_RegisteredActions.TryGetValue(key, out action); + action?.Invoke(value); + } + + /// + /// Sets one of the float properties of the environment. This data will be sent to Python. + /// + /// The string identifier of the property. + /// The float value of the property. + public void Set(string key, float value) + { + m_FloatProperties[key] = value; + using (var msgOut = new OutgoingMessage()) + { + msgOut.WriteString(key); + msgOut.WriteFloat32(value); + QueueMessageToSend(msgOut); + } + + Action action; + m_RegisteredActions.TryGetValue(key, out action); + action?.Invoke(value); + } + + /// + /// 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. + /// + /// The string identifier of the property. + /// The default value of the property. + /// The parameter value associated with the provided key. + public float GetWithDefault(string key, float defaultValue) + { + float valueOut; + bool hasKey = m_FloatProperties.TryGetValue(key, out valueOut); + return hasKey ? valueOut : defaultValue; + } + + /// + /// Registers an action to be performed everytime the property is changed. + /// + /// The string identifier of the property. + /// The action that ill be performed. Takes a float as input. + public void RegisterCallback(string key, Action action) + { + m_RegisteredActions[key] = action; + } + + /// + /// Returns a list of all the string identifiers of the properties currently present. + /// + /// The list of string identifiers + public IList Keys() + { + return new List(m_FloatProperties.Keys); + } + } +} diff --git a/com.unity.ml-agents/Runtime/SideChannels/FloatPropertiesChannel.cs.meta b/com.unity.ml-agents/Runtime/SideChannels/FloatPropertiesChannel.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..d4b87eb1e4d9f38407e6894ab380a998f2f26327 Binary files /dev/null and b/com.unity.ml-agents/Runtime/SideChannels/FloatPropertiesChannel.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/SideChannels/IncomingMessage.cs b/com.unity.ml-agents/Runtime/SideChannels/IncomingMessage.cs new file mode 100644 index 0000000000000000000000000000000000000000..f6991f76d9354a9f8938d7709c3202be61792c95 --- /dev/null +++ b/com.unity.ml-agents/Runtime/SideChannels/IncomingMessage.cs @@ -0,0 +1,127 @@ +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System; +using System.IO; +using System.Text; + +namespace Unity.MLAgents.SideChannels +{ + /// + /// Utility class for reading the data sent to the SideChannel. + /// + public class IncomingMessage : IDisposable + { + byte[] m_Data; + Stream m_Stream; + BinaryReader m_Reader; + + /// + /// Construct an IncomingMessage from the byte array. + /// + /// Byte array + public IncomingMessage(byte[] data) + { + m_Data = data; + m_Stream = new MemoryStream(data); + m_Reader = new BinaryReader(m_Stream); + } + + /// + /// Read a boolean value from the message. + /// + /// Default value to use if the end of the message is reached. + /// True if boolean was read by the reader, False if not. + public bool ReadBoolean(bool defaultValue = false) + { + return CanReadMore() ? m_Reader.ReadBoolean() : defaultValue; + } + + /// + /// Read an integer value from the message. + /// + /// Default value to use if the end of the message is reached. + /// True if int32 was read by the reader, False if not. + public int ReadInt32(int defaultValue = 0) + { + return CanReadMore() ? m_Reader.ReadInt32() : defaultValue; + } + + /// + /// Read a float value from the message. + /// + /// Default value to use if the end of the message is reached. + /// True if float32 was read by the reader, False if not. + public float ReadFloat32(float defaultValue = 0.0f) + { + return CanReadMore() ? m_Reader.ReadSingle() : defaultValue; + } + + /// + /// Read a string value from the message. + /// + /// Default value to use if the end of the message is reached. + /// True if string was read by the reader, False if not. + public string ReadString(string defaultValue = default) + { + if (!CanReadMore()) + { + return defaultValue; + } + + var strLength = ReadInt32(); + var str = Encoding.ASCII.GetString(m_Reader.ReadBytes(strLength)); + return str; + } + + /// + /// Reads a list of floats from the message. The length of the list is stored in the message. + /// + /// Default value to use if the end of the message is reached. + /// True if list of float was read by the reader, False if not. + public IList ReadFloatList(IList defaultValue = default) + { + if (!CanReadMore()) + { + return defaultValue; + } + + var len = ReadInt32(); + var output = new float[len]; + for (var i = 0; i < len; i++) + { + output[i] = ReadFloat32(); + } + + return output; + } + + /// + /// Gets the original data of the message. Note that this will return all of the data, + /// even if part of it has already been read. + /// + /// Original data of the message. + public byte[] GetRawBytes() + { + return m_Data; + } + + /// + /// Clean up the internal storage. + /// + public void Dispose() + { + m_Reader?.Dispose(); + m_Stream?.Dispose(); + } + + /// + /// Whether or not there is more data left in the stream that can be read. + /// + /// True if there is still data left in the stream that can be read, False if not. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + bool CanReadMore() + { + return m_Stream.Position < m_Stream.Length; + } + } +} diff --git a/com.unity.ml-agents/Runtime/SideChannels/IncomingMessage.cs.meta b/com.unity.ml-agents/Runtime/SideChannels/IncomingMessage.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..f70c658d1a2931367cd3437bfdd83b6f219eef17 Binary files /dev/null and b/com.unity.ml-agents/Runtime/SideChannels/IncomingMessage.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/SideChannels/OutgoingMessage.cs b/com.unity.ml-agents/Runtime/SideChannels/OutgoingMessage.cs new file mode 100644 index 0000000000000000000000000000000000000000..7f00b90e746689db003bba7da7cba07e1309b590 --- /dev/null +++ b/com.unity.ml-agents/Runtime/SideChannels/OutgoingMessage.cs @@ -0,0 +1,110 @@ +using System.Collections.Generic; +using System; +using System.IO; +using System.Text; + +namespace Unity.MLAgents.SideChannels +{ + /// + /// Utility class for forming the data that is sent to the SideChannel. + /// + public class OutgoingMessage : IDisposable + { + BinaryWriter m_Writer; + MemoryStream m_Stream; + + /// + /// Create a new empty OutgoingMessage. + /// + public OutgoingMessage() + { + m_Stream = new MemoryStream(); + m_Writer = new BinaryWriter(m_Stream); + } + + /// + /// Clean up the internal storage. + /// + public void Dispose() + { + m_Writer?.Dispose(); + m_Stream?.Dispose(); + } + + /// + /// Write a boolean value to the message. + /// + /// Boolean value + public void WriteBoolean(bool b) + { + m_Writer.Write(b); + } + + /// + /// Write an integer value to the message. + /// + /// Integer value + public void WriteInt32(int i) + { + m_Writer.Write(i); + } + + /// + /// Write a float values to the message. + /// + /// Float value + public void WriteFloat32(float f) + { + m_Writer.Write(f); + } + + /// + /// Write a string value to the message. + /// + /// String value + public void WriteString(string s) + { + var stringEncoded = Encoding.ASCII.GetBytes(s); + m_Writer.Write(stringEncoded.Length); + m_Writer.Write(stringEncoded); + } + + /// + /// Write a list or array of floats to the message. + /// + /// Float list + public void WriteFloatList(IList floatList) + { + WriteInt32(floatList.Count); + foreach (var f in floatList) + { + WriteFloat32(f); + } + } + + /// + /// Overwrite the message with a specific byte array. + /// + /// Data + public void SetRawBytes(byte[] data) + { + // Reset first. Set the length to zero so that if there's more data than we're going to + // write, we don't have any of the original data. + m_Stream.Seek(0, SeekOrigin.Begin); + m_Stream.SetLength(0); + + // Then append the data. Increase the capacity if needed (but don't shrink it). + m_Stream.Capacity = (m_Stream.Capacity < data.Length) ? data.Length : m_Stream.Capacity; + m_Stream.Write(data, 0, data.Length); + } + + /// + /// Read the byte array of the message. + /// + /// The byte array of the message. + internal byte[] ToByteArray() + { + return m_Stream.ToArray(); + } + } +} diff --git a/com.unity.ml-agents/Runtime/SideChannels/OutgoingMessage.cs.meta b/com.unity.ml-agents/Runtime/SideChannels/OutgoingMessage.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..348b80de80bd043fc5c9879c24100421a3330579 Binary files /dev/null and b/com.unity.ml-agents/Runtime/SideChannels/OutgoingMessage.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/SideChannels/RawBytesChannel.cs b/com.unity.ml-agents/Runtime/SideChannels/RawBytesChannel.cs new file mode 100644 index 0000000000000000000000000000000000000000..133832447b9d6578cbd45db0c366238cacf8d49a --- /dev/null +++ b/com.unity.ml-agents/Runtime/SideChannels/RawBytesChannel.cs @@ -0,0 +1,70 @@ +using System.Collections.Generic; +using System; + +namespace Unity.MLAgents.SideChannels +{ + /// + /// Side channel for managing raw bytes of data. It is up to the clients of this side channel + /// to interpret the messages. + /// + public class RawBytesChannel : SideChannel + { + List m_MessagesReceived = new List(); + + /// + /// RawBytesChannel provides a way to exchange raw byte arrays between Unity and Python. + /// + /// The identifier for the RawBytesChannel. Must be + /// the same on Python and Unity. + public RawBytesChannel(Guid channelId) + { + ChannelId = channelId; + } + + /// + protected override void OnMessageReceived(IncomingMessage msg) + { + m_MessagesReceived.Add(msg.GetRawBytes()); + } + + /// + /// Sends the byte array message to the Python side channel. The message will be sent + /// alongside the simulation step. + /// + /// The byte array of data to send to Python. + public void SendRawBytes(byte[] data) + { + using (var msg = new OutgoingMessage()) + { + msg.SetRawBytes(data); + QueueMessageToSend(msg); + } + } + + /// + /// Gets the messages that were sent by python since the last call to + /// GetAndClearReceivedMessages. + /// + /// a list of byte array messages that Python has sent. + public IList GetAndClearReceivedMessages() + { + var result = new List(); + result.AddRange(m_MessagesReceived); + m_MessagesReceived.Clear(); + return result; + } + + /// + /// Gets the messages that were sent by python since the last call to + /// GetAndClearReceivedMessages. Note that the messages received will not + /// be cleared with a call to GetReceivedMessages. + /// + /// a list of byte array messages that Python has sent. + public IList GetReceivedMessages() + { + var result = new List(); + result.AddRange(m_MessagesReceived); + return result; + } + } +} diff --git a/com.unity.ml-agents/Runtime/SideChannels/RawBytesChannel.cs.meta b/com.unity.ml-agents/Runtime/SideChannels/RawBytesChannel.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..90a49234ba4e36af1ac3cc4c47a7b180c019b6d3 Binary files /dev/null and b/com.unity.ml-agents/Runtime/SideChannels/RawBytesChannel.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/SideChannels/SideChannel.cs b/com.unity.ml-agents/Runtime/SideChannels/SideChannel.cs new file mode 100644 index 0000000000000000000000000000000000000000..fc94c72c31314b8cbe0907fe0256d9547b00a3c4 --- /dev/null +++ b/com.unity.ml-agents/Runtime/SideChannels/SideChannel.cs @@ -0,0 +1,69 @@ +using System.Collections.Generic; +using System; +using UnityEngine; + +namespace Unity.MLAgents.SideChannels +{ + /// + /// 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 ) and another in Python by extending a Python class + /// also called SideChannel. Then, within your project, use + /// and + /// to register and unregister your + /// custom side channel. + /// + public abstract class SideChannel + { + // The list of messages (byte arrays) that need to be sent to Python via the communicator. + // Should only ever be read and cleared by a ICommunicator object. + internal List MessageQueue = new List(); + + /// + /// An int identifier for the SideChannel. Ensures that there is only ever one side channel + /// of each type. Ensure the Unity side channels will be linked to their Python equivalent. + /// + /// The integer identifier of the SideChannel. + public Guid ChannelId + { + get; + protected set; + } + + internal void ProcessMessage(byte[] msg) + { + try + { + using (var incomingMsg = new IncomingMessage(msg)) + { + OnMessageReceived(incomingMsg); + } + } + catch (Exception ex) + { + // Catch all errors in the sidechannel processing, so that a single + // bad SideChannel implementation doesn't take everything down with it. + Debug.LogError($"Error processing SideChannel message: {ex}.\nThe message will be skipped."); + } + } + + /// + /// Is called by the communicator every time a message is received from Python by the SideChannel. + /// Can be called multiple times per simulation step if multiple messages were sent. + /// + /// The incoming message. + protected abstract void OnMessageReceived(IncomingMessage msg); + + /// + /// Queues a message to be sent to Python during the next simulation step. + /// + /// The byte array of data to be sent to Python. + protected void QueueMessageToSend(OutgoingMessage msg) + { + MessageQueue.Add(msg.ToByteArray()); + } + } +} diff --git a/com.unity.ml-agents/Runtime/SideChannels/SideChannel.cs.meta b/com.unity.ml-agents/Runtime/SideChannels/SideChannel.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..c668b0187ffb2ec337c78aa2a3f3778f61b33e39 Binary files /dev/null and b/com.unity.ml-agents/Runtime/SideChannels/SideChannel.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/SideChannels/SideChannelManager.cs b/com.unity.ml-agents/Runtime/SideChannels/SideChannelManager.cs new file mode 100644 index 0000000000000000000000000000000000000000..396e8df1d47fef884ec9444ed0e1e1ce5a12a3ff --- /dev/null +++ b/com.unity.ml-agents/Runtime/SideChannels/SideChannelManager.cs @@ -0,0 +1,251 @@ +using System; +using System.Collections.Generic; +using UnityEngine; +using System.IO; + +namespace Unity.MLAgents.SideChannels +{ + /// + /// Collection of static utilities for managing the registering/unregistering of + /// and the sending/receiving of messages for all the channels. + /// + public static class SideChannelManager + { + static Dictionary s_RegisteredChannels = new Dictionary(); + + struct CachedSideChannelMessage + { + public Guid ChannelId; + public byte[] Message; + } + + static readonly Queue s_CachedMessages = + new Queue(); + +#if UNITY_EDITOR + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] + static void ResetStaticsOnLoad() + { + s_RegisteredChannels = new Dictionary(); + } +#endif + /// + /// 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. + /// + /// The side channel to register. + public static void RegisterSideChannel(SideChannel sideChannel) + { + var channelId = sideChannel.ChannelId; + if (s_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 = s_CachedMessages.Count; + for (var i = 0; i < numMessages; i++) + { + var cachedMessage = s_CachedMessages.Dequeue(); + if (channelId == cachedMessage.ChannelId) + { + sideChannel.ProcessMessage(cachedMessage.Message); + } + else + { + s_CachedMessages.Enqueue(cachedMessage); + } + } + s_RegisteredChannels.Add(channelId, sideChannel); + } + + /// + /// 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 . Furthermore, + /// those messages will not be buffered and will, in essence, be lost. + /// + /// The side channel to unregister. + public static void UnregisterSideChannel(SideChannel sideChannel) + { + if (s_RegisteredChannels.ContainsKey(sideChannel.ChannelId)) + { + s_RegisteredChannels.Remove(sideChannel.ChannelId); + } + } + + /// + /// Unregisters all the side channels from the communicator. + /// + internal static void UnregisterAllSideChannels() + { + s_RegisteredChannels = new Dictionary(); + } + + /// + /// 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. + /// + /// + /// SideChannel if there is one registered. + internal static T GetSideChannel() where T : SideChannel + { + foreach (var sc in s_RegisteredChannels.Values) + { + if (sc.GetType() == typeof(T)) + { + return (T)sc; + } + } + return null; + } + + /// + /// Grabs the messages that the registered side channels will send to Python at the current step + /// into a singe byte array. + /// + /// The message that the registered side channels will send to Python at the current step. + internal static byte[] GetSideChannelMessage() + { + return GetSideChannelMessage(s_RegisteredChannels); + } + + /// + /// Grabs the messages that the registered side channels will send to Python at the current step + /// into a singe byte array. + /// + /// A dictionary of channel type to channel. + /// The message that the registered side channels will send to Python at the current step. + internal static byte[] GetSideChannelMessage(Dictionary sideChannels) + { + if (!HasOutgoingMessages(sideChannels)) + { + // Early out so that we don't create the MemoryStream or BinaryWriter. + // This is the most common case. + return Array.Empty(); + } + + 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(); + } + } + } + + /// + /// Check whether any of the sidechannels have queued messages. + /// + /// + /// True if the sidechannel has queued messages, False if not. + static bool HasOutgoingMessages(Dictionary sideChannels) + { + foreach (var sideChannel in sideChannels.Values) + { + var messageList = sideChannel.MessageQueue; + if (messageList.Count > 0) + { + return true; + } + } + + return false; + } + + /// + /// Separates the data received from Python into individual messages for each registered side channel. + /// + /// The byte array of data received from Python. + internal static void ProcessSideChannelData(byte[] dataReceived) + { + ProcessSideChannelData(s_RegisteredChannels, dataReceived); + } + + /// + /// Separates the data received from Python into individual messages for each registered side channel. + /// + /// A dictionary of channel type to channel. + /// The byte array of data received from Python. + internal static void ProcessSideChannelData(Dictionary sideChannels, byte[] dataReceived) + { + while (s_CachedMessages.Count != 0) + { + var cachedMessage = s_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. + s_CachedMessages.Enqueue(new CachedSideChannelMessage + { + ChannelId = channelId, + Message = message + }); + } + } + } + } + } + } +} diff --git a/com.unity.ml-agents/Runtime/SideChannels/SideChannelManager.cs.meta b/com.unity.ml-agents/Runtime/SideChannels/SideChannelManager.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..251cc146329425acddc70f731ca9cb7b1bcbd5cc Binary files /dev/null and b/com.unity.ml-agents/Runtime/SideChannels/SideChannelManager.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/SideChannels/StatsSideChannel.cs b/com.unity.ml-agents/Runtime/SideChannels/StatsSideChannel.cs new file mode 100644 index 0000000000000000000000000000000000000000..dbe1b5aeec3c8d4e2865eea60dcfa6fcf36fccad --- /dev/null +++ b/com.unity.ml-agents/Runtime/SideChannels/StatsSideChannel.cs @@ -0,0 +1,43 @@ +using System; +namespace Unity.MLAgents.SideChannels +{ + /// + /// A Side Channel for sending data. + /// + internal class StatsSideChannel : SideChannel + { + const string k_StatsSideChannelDefaultId = "a1d8f7b7-cec8-50f9-b78b-d3e165a78520"; + + /// + /// Initializes the side channel. The constructor is internal because only one instance is + /// supported at a time. + /// + internal StatsSideChannel() + { + ChannelId = new Guid(k_StatsSideChannelDefaultId); + } + + /// + /// Add a stat value for reporting. + /// + /// The stat name. + /// The stat value. + /// How multiple values should be treated. + public void AddStat(string key, float value, StatAggregationMethod aggregationMethod) + { + using (var msg = new OutgoingMessage()) + { + msg.WriteString(key); + msg.WriteFloat32(value); + msg.WriteInt32((int)aggregationMethod); + QueueMessageToSend(msg); + } + } + + /// + protected override void OnMessageReceived(IncomingMessage msg) + { + throw new UnityAgentsException("StatsSideChannel should never receive messages."); + } + } +} diff --git a/com.unity.ml-agents/Runtime/SideChannels/StatsSideChannel.cs.meta b/com.unity.ml-agents/Runtime/SideChannels/StatsSideChannel.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..ebc11e7092a6d4d376f227bcfbc171a79aff0de7 Binary files /dev/null and b/com.unity.ml-agents/Runtime/SideChannels/StatsSideChannel.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/SideChannels/TrainingAnalyticsSideChannel.cs b/com.unity.ml-agents/Runtime/SideChannels/TrainingAnalyticsSideChannel.cs new file mode 100644 index 0000000000000000000000000000000000000000..0c880e4be991989000694dd69aa501452ed73f84 --- /dev/null +++ b/com.unity.ml-agents/Runtime/SideChannels/TrainingAnalyticsSideChannel.cs @@ -0,0 +1,52 @@ +using System; +using Unity.MLAgents.Analytics; +using Unity.MLAgents.CommunicatorObjects; + +namespace Unity.MLAgents.SideChannels +{ + /// + /// Side Channel implementation for recording which training features are being used. + /// + internal class TrainingAnalyticsSideChannel : SideChannel + { + const string k_TrainingAnalyticsConfigId = "b664a4a9-d86f-5a5f-95cb-e8353a7e8356"; + + /// + /// Initializes the side channel. The constructor is internal because only one instance is + /// supported at a time, and is created by the Academy. + /// + internal TrainingAnalyticsSideChannel() + { + ChannelId = new Guid(k_TrainingAnalyticsConfigId); + } + + /// + protected override void OnMessageReceived(IncomingMessage msg) + { + Google.Protobuf.WellKnownTypes.Any anyMessage = null; + try + { + anyMessage = Google.Protobuf.WellKnownTypes.Any.Parser.ParseFrom(msg.GetRawBytes()); + } + catch (Google.Protobuf.InvalidProtocolBufferException) + { + // Bad message, nothing we can do about it, so just ignore. + return; + } + + if (anyMessage.Is(TrainingEnvironmentInitialized.Descriptor)) + { + var envInitProto = anyMessage.Unpack(); + var envInitEvent = envInitProto.ToTrainingEnvironmentInitializedEvent(); + TrainingAnalytics.TrainingEnvironmentInitialized(envInitEvent); + } + else if (anyMessage.Is(TrainingBehaviorInitialized.Descriptor)) + { + var behaviorInitProto = anyMessage.Unpack(); + var behaviorTrainingEvent = behaviorInitProto.ToTrainingBehaviorInitializedEvent(); + TrainingAnalytics.TrainingBehaviorInitialized(behaviorTrainingEvent); + } + // Don't do anything for unknown types, since the user probably can't do anything about it. + } + } +} diff --git a/com.unity.ml-agents/Runtime/SideChannels/TrainingAnalyticsSideChannel.cs.meta b/com.unity.ml-agents/Runtime/SideChannels/TrainingAnalyticsSideChannel.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..757d0d0d4fbb697a2a83214b1d71d6a30e83e514 Binary files /dev/null and b/com.unity.ml-agents/Runtime/SideChannels/TrainingAnalyticsSideChannel.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/SimpleMultiAgentGroup.cs b/com.unity.ml-agents/Runtime/SimpleMultiAgentGroup.cs new file mode 100644 index 0000000000000000000000000000000000000000..c5fe6ce83587b268e8b4cf557e984900f60d2959 --- /dev/null +++ b/com.unity.ml-agents/Runtime/SimpleMultiAgentGroup.cs @@ -0,0 +1,145 @@ +using System; +using System.Linq; +using System.Collections.Generic; + +namespace Unity.MLAgents +{ + /// + /// A basic class implementation of MultiAgentGroup. + /// + public class SimpleMultiAgentGroup : IMultiAgentGroup, IDisposable + { + readonly int m_Id = MultiAgentGroupIdCounter.GetGroupId(); + HashSet m_Agents = new HashSet(); + + /// + /// Disposes of the SimpleMultiAgentGroup. + /// + public virtual void Dispose() + { + while (m_Agents.Count > 0) + { + UnregisterAgent(m_Agents.First()); + } + } + + /// + public virtual void RegisterAgent(Agent agent) + { + if (!m_Agents.Contains(agent)) + { + agent.SetMultiAgentGroup(this); + m_Agents.Add(agent); + agent.OnAgentDisabled += UnregisterAgent; + } + } + + /// + public virtual void UnregisterAgent(Agent agent) + { + if (m_Agents.Contains(agent)) + { + agent.SetMultiAgentGroup(null); + m_Agents.Remove(agent); + agent.OnAgentDisabled -= UnregisterAgent; + } + } + + /// + public int GetId() + { + return m_Id; + } + + /// + /// Get list of all agents currently registered to this MultiAgentGroup. + /// + /// + /// List of agents registered to the MultiAgentGroup. + /// + public IReadOnlyCollection GetRegisteredAgents() + { + return m_Agents; + } + + /// + /// Increments the group rewards for all agents in this MultiAgentGroup. + /// + /// + /// This function increases or decreases the group rewards by a given amount for all agents + /// in the group. Use to set the group reward assigned + /// to the current step with a specific value rather than increasing or decreasing it. + /// + /// A positive group reward indicates the whole group's accomplishments or desired behaviors. + /// Every agent in the group will receive the same group reward no matter whether the + /// agent's act directly leads to the reward. Group rewards are meant to reinforce agents + /// to act in the group's best interest instead of individual ones. + /// Group rewards are treated differently than individual agent rewards during training, so + /// calling AddGroupReward() is not equivalent to calling agent.AddReward() on each agent in the group. + /// + /// Incremental group reward value. + public void AddGroupReward(float reward) + { + foreach (var agent in m_Agents) + { + agent.AddGroupReward(reward); + } + } + + /// + /// Set the group rewards for all agents in this MultiAgentGroup. + /// + /// + /// This function replaces any group rewards given during the current step for all agents in the group. + /// Use to incrementally change the group reward rather than + /// overriding it. + /// + /// A positive group reward indicates the whole group's accomplishments or desired behaviors. + /// Every agent in the group will receive the same group reward no matter whether the + /// agent's act directly leads to the reward. Group rewards are meant to reinforce agents + /// to act in the group's best interest instead of indivisual ones. + /// Group rewards are treated differently than individual agent rewards during training, so + /// calling SetGroupReward() is not equivalent to calling agent.SetReward() on each agent in the group. + /// + /// The new value of the group reward. + public void SetGroupReward(float reward) + { + foreach (var agent in m_Agents) + { + agent.SetGroupReward(reward); + } + } + + /// + /// End episodes for all agents in this MultiAgentGroup. + /// + /// + /// This should be used when the episode can no longer continue, such as when the group + /// reaches the goal or fails at the task. + /// + public void EndGroupEpisode() + { + foreach (var agent in m_Agents) + { + agent.EndEpisode(); + } + } + + /// + /// Indicate that the episode is over but not due to the "fault" of the group. + /// This has the same end result as calling , but has a + /// slightly different effect on training. + /// + /// + /// This should be used when the episode could continue, but has gone on for + /// a sufficient number of steps, such as if the environment hits some maximum number of steps. + /// + public void GroupEpisodeInterrupted() + { + foreach (var agent in m_Agents) + { + agent.EpisodeInterrupted(); + } + } + } +} diff --git a/com.unity.ml-agents/Runtime/SimpleMultiAgentGroup.cs.meta b/com.unity.ml-agents/Runtime/SimpleMultiAgentGroup.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..33b0a0559e2a6b80935e1cb13facdb25dcf195b9 Binary files /dev/null and b/com.unity.ml-agents/Runtime/SimpleMultiAgentGroup.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/StatsRecorder.cs b/com.unity.ml-agents/Runtime/StatsRecorder.cs new file mode 100644 index 0000000000000000000000000000000000000000..d7250862b975eeb161f57b1d55ade1990fc526ae --- /dev/null +++ b/com.unity.ml-agents/Runtime/StatsRecorder.cs @@ -0,0 +1,80 @@ +using Unity.MLAgents.SideChannels; + +namespace Unity.MLAgents +{ + /// + /// Determines the behavior of how multiple stats within the same summary period are combined. + /// + public enum StatAggregationMethod + { + /// + /// Values within the summary period are averaged before reporting. + /// + Average = 0, + + /// + /// 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. + /// + MostRecent = 1, + + /// + /// Values within the summary period are summed up before reporting. + /// + Sum = 2, + + /// + /// Values within the summary period are reported as a histogram. + /// + Histogram = 3 + } + + /// + /// 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 provided. + /// + public sealed class StatsRecorder + { + /// + /// The side channel that is used to receive the new parameter values. + /// + readonly StatsSideChannel m_Channel; + + /// + /// Constructor. + /// + internal StatsRecorder() + { + m_Channel = new StatsSideChannel(); + SideChannelManager.RegisterSideChannel(m_Channel); + } + + /// + /// Add a stat value for reporting. + /// + /// The stat name. + /// + /// The stat value. You can nest stats in TensorBoard by using "/". + /// + /// + /// How multiple values sent in the same summary window should be treated. + /// + public void Add( + string key, + float value, + StatAggregationMethod aggregationMethod = StatAggregationMethod.Average) + { + m_Channel.AddStat(key, value, aggregationMethod); + } + + internal void Dispose() + { + SideChannelManager.UnregisterSideChannel(m_Channel); + } + } +} diff --git a/com.unity.ml-agents/Runtime/StatsRecorder.cs.meta b/com.unity.ml-agents/Runtime/StatsRecorder.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..bfc4addbb19fa2c4afcc73fc56cf673cb89af044 Binary files /dev/null and b/com.unity.ml-agents/Runtime/StatsRecorder.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Timer.cs b/com.unity.ml-agents/Runtime/Timer.cs new file mode 100644 index 0000000000000000000000000000000000000000..a519ed06c32ad8b78d249644eb2d05f48a6be54a --- /dev/null +++ b/com.unity.ml-agents/Runtime/Timer.cs @@ -0,0 +1,534 @@ +// Compile with: csc CRefTest.cs -doc:Results.xml +#if UNITY_EDITOR || UNITY_STANDALONE +#define MLA_SUPPORTED_TRAINING_PLATFORM +#endif +using System; +using UnityEngine; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UnityEngine.Profiling; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Json; +using UnityEngine.SceneManagement; + +namespace Unity.MLAgents +{ + [DataContract] + internal class TimerNode + { + const string k_Separator = "."; + const double k_TicksToSeconds = 1e-7; // 100 ns per tick + + /// + /// Full name of the node. This is the node's parents full name concatenated with this + /// node's name. + /// + string m_FullName; + + /// + /// Child nodes, indexed by name. + /// + [DataMember(Name = "children", Order = 999)] + Dictionary m_Children; + + /// + /// Custom sampler used to add timings to the profiler. + /// + CustomSampler m_Sampler; + + /// + /// Number of total ticks elapsed for this node. + /// + long m_TotalTicks; + + /// + /// If the node is currently running, the time (in ticks) when the node was started. + /// If the node is not running, is set to 0. + /// + long m_TickStart; + + /// + /// Number of times the corresponding code block has been called. + /// + [DataMember(Name = "count")] + int m_NumCalls; + + /// + /// The total recorded ticks for the timer node, plus the currently elapsed ticks + /// if the timer is still running (i.e. if m_TickStart is non-zero). + /// + public long CurrentTicks + { + get + { + var currentTicks = m_TotalTicks; + if (m_TickStart != 0) + { + currentTicks += (DateTime.Now.Ticks - m_TickStart); + } + + return currentTicks; + } + } + + /// + /// Total elapsed seconds. + /// + [DataMember(Name = "total")] + public double TotalSeconds + { + get { return CurrentTicks * k_TicksToSeconds; } + set { } // Serialization needs this, but unused. + } + + /// + /// Total seconds spent in this block, excluding it's children. + /// + [DataMember(Name = "self")] + public double SelfSeconds + { + get + { + long totalChildTicks = 0; + if (m_Children != null) + { + foreach (var child in m_Children.Values) + { + totalChildTicks += child.m_TotalTicks; + } + } + + var selfTicks = Mathf.Max(0, CurrentTicks - totalChildTicks); + return selfTicks * k_TicksToSeconds; + } + set { } // Serialization needs this, but unused. + } + + public IReadOnlyDictionary Children + { + get { return m_Children; } + } + + public int NumCalls + { + get { return m_NumCalls; } + } + + public TimerNode(string name, bool isRoot = false) + { + m_FullName = name; + if (isRoot) + { + // The root node is considered always running. This means that when we output stats, it'll + // have a sensible value for total time (the running time since reset). + // The root node doesn't have a sampler since that could interfere with the profiler. + m_NumCalls = 1; + m_TickStart = DateTime.Now.Ticks; + } + else + { + m_Sampler = CustomSampler.Create(m_FullName); + } + } + + /// + /// Start timing a block of code. + /// + public void Begin() + { + m_Sampler?.Begin(); + m_TickStart = DateTime.Now.Ticks; + } + + /// + /// Stop timing a block of code, and increment internal counts. + /// + public void End() + { + var elapsed = DateTime.Now.Ticks - m_TickStart; + m_TotalTicks += elapsed; + m_TickStart = 0; + m_NumCalls++; + m_Sampler?.End(); + } + + /// + /// Return a child node for the given name. + /// The children dictionary will be created if it does not already exist, and + /// a new Node will be created if it's not already in the dictionary. + /// Note that these allocations only happen once for a given timed block. + /// + /// + /// The `TimerNode` child node. + public TimerNode GetChild(string name) + { + // Lazily create the children dictionary. + if (m_Children == null) + { + m_Children = new Dictionary(); + } + + if (!m_Children.ContainsKey(name)) + { + var childFullName = m_FullName + k_Separator + name; + var newChild = new TimerNode(childFullName); + m_Children[name] = newChild; + return newChild; + } + + return m_Children[name]; + } + + /// + /// Recursively form a string representing the current timer information. + /// + /// + /// + /// The string summary of the `TimerNode`. + public string DebugGetTimerString(string parentName = "", int level = 0) + { + var indent = new string(' ', 2 * level); // TODO generalize + var shortName = (level == 0) ? m_FullName : m_FullName.Replace(parentName + k_Separator, ""); + string timerString; + if (level == 0) + { + timerString = $"{shortName}(root)\n"; + } + else + { + timerString = $"{indent}{shortName}\t\traw={TotalSeconds} rawCount={m_NumCalls}\n"; + } + + // TODO use StringBuilder? might be overkill since this is only debugging code? + if (m_Children != null) + { + foreach (var c in m_Children.Values) + { + timerString += c.DebugGetTimerString(m_FullName, level + 1); + } + } + return timerString; + } + } + + [DataContract] + internal class RootNode : TimerNode + { + // Timer output format version + internal const string k_TimerFormatVersion = "0.1.0"; + + [DataMember(Name = "metadata", Order = 0)] + Dictionary m_Metadata = new Dictionary(); + + /// + /// Gauge Nodes to measure arbitrary values. + /// + [DataMember(Name = "gauges", EmitDefaultValue = false)] + Dictionary m_Gauges = new Dictionary(); + + public RootNode(string name = "root") : base(name, true) + { + m_Metadata.Add("timer_format_version", k_TimerFormatVersion); + m_Metadata.Add("start_time_seconds", $"{DateTimeOffset.Now.ToUnixTimeSeconds()}"); + m_Metadata.Add("unity_version", Application.unityVersion); + m_Metadata.Add("command_line_arguments", String.Join(" ", GetCleanedCommandLineArguments())); + } + + /// + /// Cleans Environment CommandLine Argument from license infos + /// + /// cleaned string list of commandLine + private static List GetCleanedCommandLineArguments() + { + List commandLineArgs = Environment.GetCommandLineArgs().ToList(); + List toRemoveIndices = new List { }; + for (var i = 0; i < commandLineArgs.Count; i++) + { + if (commandLineArgs[i].Contains("accessToken") || + commandLineArgs[i].Contains("hubSessionId") || + commandLineArgs[i].Contains("licensingIpc")) + { + toRemoveIndices.Add(i); + toRemoveIndices.Add(i + 1); + } + } + // remove in reverse order + for (var i = toRemoveIndices.Count() - 1; i >= 0; i--) + { + commandLineArgs.RemoveAt(toRemoveIndices[i]); + } + return commandLineArgs; + } + + public void AddMetadata(string key, string value) + { + m_Metadata[key] = value; + } + + public Dictionary Gauges + { + get { return m_Gauges; } + } + + public Dictionary Metadata + { + get { return m_Metadata; } + } + } + + /// + /// Tracks the most recent value of a metric. This is analogous to gauges in statsd and Prometheus. + /// + [DataContract] + internal class GaugeNode + { + const float k_SmoothingFactor = .25f; // weight for exponential moving average. + + /// + /// The most recent value that the gauge was set to. + /// + [DataMember] + public float value; + + /// + /// The smallest value that has been seen for the gauge since it was created. + /// + [DataMember(Name = "min")] + public float minValue; + + /// + /// The largest value that has been seen for the gauge since it was created. + /// + [DataMember(Name = "max")] + public float maxValue; + + /// + /// The exponential moving average of the gauge value. This will take all values into account, + /// but weights older values less as more values are added. + /// + [DataMember(Name = "weightedAverage")] + public float weightedAverage; + + /// + /// The running average of all gauge values. + /// + [DataMember] + public float runningAverage; + + /// + /// The number of times the gauge has been updated. + /// + [DataMember] + public uint count; + + public GaugeNode(float value) + { + this.value = value; + weightedAverage = value; + runningAverage = value; + minValue = value; + maxValue = value; + count = 1; + } + + public void Update(float newValue) + { + ++count; + minValue = Mathf.Min(minValue, newValue); + maxValue = Mathf.Max(maxValue, newValue); + // update exponential moving average + weightedAverage = (k_SmoothingFactor * newValue) + ((1f - k_SmoothingFactor) * weightedAverage); + value = newValue; + + // Update running average - see https://www.johndcook.com/blog/standard_deviation/ for formula. + runningAverage = runningAverage + (newValue - runningAverage) / count; + } + } + + /// + /// A "stack" of timers that allows for lightweight hierarchical profiling of long-running processes. + /// + /// Example usage: + /// + /// using(TimerStack.Instance.Scoped("foo")) + /// { + /// doSomeWork(); + /// for (int i=0; i<5; i++) + /// { + /// using(myTimer.Scoped("bar")) + /// { + /// doSomeMoreWork(); + /// } + /// } + /// } + /// + /// + /// + /// + /// This implements the Singleton pattern (solution 4) as described in + /// https://csharpindepth.com/articles/singleton + /// + internal class TimerStack : IDisposable + { + static readonly TimerStack k_Instance = new TimerStack(); + + Stack m_Stack; + RootNode m_RootNode; + Dictionary m_Metadata; + + // Explicit static constructor to tell C# compiler + // not to mark type as beforefieldinit + static TimerStack() + { + } + + TimerStack() + { + Reset(); + } + + /// + /// Resets the timer stack and the root node. + /// + /// Name of the root node. + public void Reset(string name = "root") + { + m_Stack = new Stack(); + m_RootNode = new RootNode(name); + m_Stack.Push(m_RootNode); + } + + /// + /// The singleton instance. + /// + public static TimerStack Instance + { + get { return k_Instance; } + } + + internal RootNode RootNode + { + get { return m_RootNode; } + } + + /// + /// Updates the referenced gauge in the root node with the provided value. + /// + /// The name of the Gauge to modify. + /// The value to update the Gauge with. + public void SetGauge(string name, float value) + { + if (!float.IsNaN(value)) + { + GaugeNode gauge; + if (m_RootNode.Gauges.TryGetValue(name, out gauge)) + { + gauge.Update(value); + } + else + { + m_RootNode.Gauges[name] = new GaugeNode(value); + } + } + } + + public void AddMetadata(string key, string value) + { + m_RootNode.AddMetadata(key, value); + } + + void Push(string name) + { + var current = m_Stack.Peek(); + var next = current.GetChild(name); + m_Stack.Push(next); + next.Begin(); + } + + void Pop() + { + var node = m_Stack.Pop(); + node.End(); + } + + /// + /// Start a scoped timer. This should be used with the "using" statement. + /// + /// + /// `TimerStack` scoped timer. + public TimerStack Scoped(string name) + { + Push(name); + return this; + } + + /// + /// Closes the current scoped timer. This should never be called directly, only + /// at the end of a "using" statement. + /// Note that the instance is not actually disposed of; this is just to allow it to be used + /// conveniently with "using". + /// + public void Dispose() + { + Pop(); + } + + /// + /// Get a string representation of the timers. + /// Potentially slow so call sparingly. + /// + /// The string summary of the `TimerStack`. + internal string DebugGetTimerString() + { + return m_RootNode.DebugGetTimerString(); + } + + /// + /// Save the timers in JSON format to the provided filename. + /// If the filename is null, a default one will be used. + /// + /// + public void SaveJsonTimers(string filename = null) + { +#if MLA_SUPPORTED_TRAINING_PLATFORM + try + { + if (filename == null) + { + var activeScene = SceneManager.GetActiveScene(); + var timerDir = Path.Combine(Application.dataPath, "ML-Agents", "Timers"); + Directory.CreateDirectory(timerDir); + + filename = Path.Combine(timerDir, $"{activeScene.name}_timers.json"); + } + + var fs = new FileStream(filename, FileMode.Create, FileAccess.Write); + SaveJsonTimers(fs); + fs.Close(); + } + catch (SystemException) + { + // We may not have write access to the directory. + Debug.LogWarning($"Unable to save timers to file {filename}"); + } +#endif + } + + /// + /// Write the timers in JSON format to the provided stream. + /// + /// + public void SaveJsonTimers(Stream stream) + { + // Add some final metadata info + AddMetadata("scene_name", SceneManager.GetActiveScene().name); + AddMetadata("end_time_seconds", $"{DateTimeOffset.Now.ToUnixTimeSeconds()}"); + + var jsonSettings = new DataContractJsonSerializerSettings(); + jsonSettings.UseSimpleDictionaryFormat = true; + var ser = new DataContractJsonSerializer(typeof(RootNode), jsonSettings); + ser.WriteObject(stream, m_RootNode); + } + } +} diff --git a/com.unity.ml-agents/Runtime/Timer.cs.meta b/com.unity.ml-agents/Runtime/Timer.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..e28315908dbd87e67a80d51e5b54d9cc5e7a19c5 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Timer.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Unity.ML-Agents.asmdef b/com.unity.ml-agents/Runtime/Unity.ML-Agents.asmdef new file mode 100644 index 0000000000000000000000000000000000000000..5926d50e62512e74f3a7205283e4f00648dd4a17 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Unity.ML-Agents.asmdef @@ -0,0 +1,38 @@ +{ + "name": "Unity.ML-Agents", + "rootNamespace": "", + "references": [ + "Unity.ML-Agents.CommunicatorObjects", + "Unity.Mathematics", + "Unity.InferenceEngine" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": true, + "precompiledReferences": [ + "System.IO.Abstractions.dll", + "Grpc.Core.dll", + "Google.Protobuf_Packed.dll" + ], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [ + { + "name": "com.unity.modules.unityanalytics", + "expression": "1.0.0", + "define": "MLA_UNITY_ANALYTICS_MODULE" + }, + { + "name": "com.unity.modules.physics", + "expression": "1.0.0", + "define": "MLA_UNITY_PHYSICS_MODULE" + }, + { + "name": "com.unity.modules.physics2d", + "expression": "1.0.0", + "define": "MLA_UNITY_PHYSICS2D_MODULE" + } + ], + "noEngineReferences": false +} diff --git a/com.unity.ml-agents/Runtime/Unity.ML-Agents.asmdef.meta b/com.unity.ml-agents/Runtime/Unity.ML-Agents.asmdef.meta new file mode 100644 index 0000000000000000000000000000000000000000..21cbeb979350d6b92953007e7275bc968e0cf703 Binary files /dev/null and b/com.unity.ml-agents/Runtime/Unity.ML-Agents.asmdef.meta differ diff --git a/com.unity.ml-agents/Runtime/UnityAgentsException.cs b/com.unity.ml-agents/Runtime/UnityAgentsException.cs new file mode 100644 index 0000000000000000000000000000000000000000..0abc1ace88d3f0fccd9a4d50b86842fad76b5f04 --- /dev/null +++ b/com.unity.ml-agents/Runtime/UnityAgentsException.cs @@ -0,0 +1,32 @@ +using System; + +namespace Unity.MLAgents +{ + /// + /// Contains exceptions specific to ML-Agents. + /// + [Serializable] + public class UnityAgentsException : Exception + { + /// + /// When a UnityAgentsException is called, the timeScale is set to 0. + /// The simulation will end since no steps will be taken. + /// + /// The exception message + public UnityAgentsException(string message) : base(message) + { + } + + /// + /// A constructor is needed for serialization when an exception propagates + /// from a remoting server to the client. + /// + /// Data for serializing/de-serializing + /// Describes the source and destination of the serialized stream + protected UnityAgentsException( + System.Runtime.Serialization.SerializationInfo info, + System.Runtime.Serialization.StreamingContext context) + { + } + } +} diff --git a/com.unity.ml-agents/Runtime/UnityAgentsException.cs.meta b/com.unity.ml-agents/Runtime/UnityAgentsException.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..f72768b1df0e18fcd98649b609690d5f833f7e1f Binary files /dev/null and b/com.unity.ml-agents/Runtime/UnityAgentsException.cs.meta differ diff --git a/com.unity.ml-agents/Runtime/Utilities.cs b/com.unity.ml-agents/Runtime/Utilities.cs new file mode 100644 index 0000000000000000000000000000000000000000..e9d442504889146c41f7ad9d7c94249014d20755 --- /dev/null +++ b/com.unity.ml-agents/Runtime/Utilities.cs @@ -0,0 +1,60 @@ +using System; +using System.Diagnostics; +using UnityEngine; + +namespace Unity.MLAgents +{ + internal static class Utilities + { + /// + /// Calculates the cumulative sum of an integer array. The result array will be one element + /// larger than the input array since it has a padded 0 at the beginning. + /// If the input is [a, b, c], the result will be [0, a, a+b, a+b+c] + /// + /// + /// Input array whose elements will be cumulatively added + /// + /// The cumulative sum of the input array. + internal static int[] CumSum(int[] input) + { + var runningSum = 0; + var result = new int[input.Length + 1]; + for (var actionIndex = 0; actionIndex < input.Length; actionIndex++) + { + runningSum += input[actionIndex]; + result[actionIndex + 1] = runningSum; + } + return result; + } + + /// + /// Safely destroy a texture. This has to be used differently in unit tests. + /// + /// + internal static void DestroyTexture(Texture2D texture) + { + if (Application.isEditor) + { + // Edit Mode tests complain if we use Destroy() + UnityEngine.Object.DestroyImmediate(texture); + } + else + { + UnityEngine.Object.Destroy(texture); + } + } + + [Conditional("DEBUG")] + internal static void DebugCheckNanAndInfinity(float value, string valueCategory, string caller) + { + if (float.IsNaN(value)) + { + throw new ArgumentException($"NaN {valueCategory} passed to {caller}."); + } + if (float.IsInfinity(value)) + { + throw new ArgumentException($"Inifinity {valueCategory} passed to {caller}."); + } + } + } +} diff --git a/com.unity.ml-agents/Runtime/Utilities.cs.meta b/com.unity.ml-agents/Runtime/Utilities.cs.meta new file mode 100644 index 0000000000000000000000000000000000000000..872088ea832498d0c22ee62c12a45611ee045cee Binary files /dev/null and b/com.unity.ml-agents/Runtime/Utilities.cs.meta differ diff --git a/com.unity.ml-agents/com.unity.ml-agents.sln.DotSettings b/com.unity.ml-agents/com.unity.ml-agents.sln.DotSettings new file mode 100644 index 0000000000000000000000000000000000000000..76282484bb531419a543e79e75c71368868dce5d --- /dev/null +++ b/com.unity.ml-agents/com.unity.ml-agents.sln.DotSettings @@ -0,0 +1,21 @@ + + BLAS + CPU + GPU + NN + PNG + RL + True + True + True + + + True + True + True + True + True + True + True + True + True diff --git a/com.unity.ml-agents/com.unity.ml-agents.sln.DotSettings.meta b/com.unity.ml-agents/com.unity.ml-agents.sln.DotSettings.meta new file mode 100644 index 0000000000000000000000000000000000000000..ee052ef017083076258de6d073b46ba517476b76 Binary files /dev/null and b/com.unity.ml-agents/com.unity.ml-agents.sln.DotSettings.meta differ diff --git a/com.unity.ml-agents/package.json b/com.unity.ml-agents/package.json new file mode 100644 index 0000000000000000000000000000000000000000..9aabed59c128373a3ce4394e6058492faf8241c4 --- /dev/null +++ b/com.unity.ml-agents/package.json @@ -0,0 +1,16 @@ +{ + "name": "com.unity.ml-agents", + "displayName": "ML Agents", + "version": "4.0.3", + "unity": "6000.0", + "description": "Use state-of-the-art machine learning to create intelligent character behaviors in any Unity environment (games, robotics, film, etc.).", + "dependencies": { + "com.unity.ai.inference": "2.6.1", + "com.unity.modules.imageconversion": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.physics": "1.0.0" + }, + "relatedPackages": { + "com.unity.ml-agents.tests": "1.0.0" + } +} diff --git a/com.unity.ml-agents/package.json.meta b/com.unity.ml-agents/package.json.meta new file mode 100644 index 0000000000000000000000000000000000000000..d76c84a5faffd8f5c34ca71b704bdfb2ef49239d Binary files /dev/null and b/com.unity.ml-agents/package.json.meta differ