diff --git a/Assets/ECS_MLAgents_v0/Core/AgentSystem.cs b/Assets/ECS_MLAgents_v0/Core/AgentSystem.cs index 2b3ed3f..2ac3d70 100644 --- a/Assets/ECS_MLAgents_v0/Core/AgentSystem.cs +++ b/Assets/ECS_MLAgents_v0/Core/AgentSystem.cs @@ -3,7 +3,6 @@ using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; using Unity.Entities; using Unity.Jobs; -using UnityEngine; namespace ECS_MLAgents_v0.Core { diff --git a/Assets/ECS_MLAgents_v0/Core/ExternalDecision.cs b/Assets/ECS_MLAgents_v0/Core/ExternalDecision.cs new file mode 100644 index 0000000..4929e06 --- /dev/null +++ b/Assets/ECS_MLAgents_v0/Core/ExternalDecision.cs @@ -0,0 +1,106 @@ +using System; +using Unity.Collections; +using Unity.Jobs; +using System.IO.MemoryMappedFiles; +using System.IO; +using Unity.Collections.LowLevel.Unsafe; +using UnityEngine; +using UnityEngine.Profiling; + + +namespace ECS_MLAgents_v0.Core{ + public class ExternalDecision : IAgentDecision + { + + // [ Unity Ready (1) , nAgents (4) , sensorSize (4) , actuatorSize (4) , Data + + // TODO : This capacity needs to scale / communicate multiple times per step ? + private const int FILE_CAPACITY = 200000; + private const int NUMBER_AGENTS_POSITION = 0; + private const int SENSOR_SIZE_POSITION = 4; + private const int ACTUATOR_SIZE_POSITION = 8; + private const int UNITY_READY_POSITION = 12; + private const int SENSOR_DATA_POSITION = 13; + + private const int PYTHON_READY_POSITION = 100000; + private const int ACTUATOR_DATA_POSITION = 100001; + + + private float[] actuatorData = new float[0]; + + // This is a temporary test file + // TODO : Replace with a file creation system + // TODO : Implement the communication in a separate class + // TODO : Have separate files for sensor and actuators + private string filenameWrite = "Assets/shared_communication_file.txt"; + + private MemoryMappedViewAccessor accessor; + + public ExternalDecision() + { + var mmf = MemoryMappedFile.CreateFromFile(filenameWrite, FileMode.Open, "Test"); + accessor = mmf.CreateViewAccessor( + 0, FILE_CAPACITY, MemoryMappedFileAccess.ReadWrite); +// accessor.WriteArray(0, new bool[FILE_CAPACITY], 0, FILE_CAPACITY); + accessor.Write(PYTHON_READY_POSITION, false); + accessor.Write(UNITY_READY_POSITION, false); + Debug.Log("Is Ready to Communicate"); + } + + public JobHandle DecideBatch( + ref NativeArray sensor, + ref NativeArray actuator, + int sensorSize, + int actuatorSize, + int nAgents, + JobHandle handle) + { + Profiler.BeginSample("Communicating"); + if (sensor.Length > 4 * 50000) + { + throw new Exception("TOO much data to send"); + } + + if (actuator.Length > 4 * 50000) + { + throw new Exception("TOO much data to send"); + } + + if (actuatorData.Length < actuator.Length) + { + actuatorData = new float[actuator.Length]; + } + + + accessor.Write(NUMBER_AGENTS_POSITION, nAgents); + accessor.Write(SENSOR_SIZE_POSITION, sensorSize); + accessor.Write(ACTUATOR_SIZE_POSITION, actuatorSize); + + accessor.WriteArray(SENSOR_DATA_POSITION, sensor.ToArray(), 0, sensor.Length); + + accessor.Write(PYTHON_READY_POSITION, false); + + accessor.Write(UNITY_READY_POSITION, true); + + + var readyToContinue = false; + int loopIter = 0; + while (!readyToContinue) + { + loopIter++; + readyToContinue = accessor.ReadBoolean(PYTHON_READY_POSITION); + readyToContinue = readyToContinue || loopIter > 200000; + if (loopIter > 200000) + { + Debug.Log("Missed Communication"); + } + } + + accessor.ReadArray(ACTUATOR_DATA_POSITION, actuatorData, 0, actuator.Length); + actuator.CopyFrom(actuatorData); + + Profiler.BeginSample("Communicating"); + return handle; + } + } +} diff --git a/Assets/ECS_MLAgents_v0/Core/ExternalDecision.cs.meta b/Assets/ECS_MLAgents_v0/Core/ExternalDecision.cs.meta new file mode 100644 index 0000000..b7c4bcb --- /dev/null +++ b/Assets/ECS_MLAgents_v0/Core/ExternalDecision.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 03d6cba5b00b14245aee6796e797ffc1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ECS_MLAgents_v0/Example/SpaceWars/Scripts/Manager.cs b/Assets/ECS_MLAgents_v0/Example/SpaceWars/Scripts/Manager.cs index 4a9f4e0..0218456 100644 --- a/Assets/ECS_MLAgents_v0/Example/SpaceWars/Scripts/Manager.cs +++ b/Assets/ECS_MLAgents_v0/Example/SpaceWars/Scripts/Manager.cs @@ -37,7 +37,7 @@ namespace ECS_MLAgents_v0.Example.SpaceWars.Scripts void Start() { - Time.captureFramerate = 60; +// Time.captureFramerate = 60; manager = World.Active.GetOrCreateManager(); _sensorSystem = World.Active.GetOrCreateManager(); @@ -45,7 +45,8 @@ namespace ECS_MLAgents_v0.Example.SpaceWars.Scripts _impactSystem.Radius = 20; _shipSystemA = World.Active.GetExistingManager(); - _shipSystemA.Decision = new NNDecision(model); +// _shipSystemA.Decision = new NNDecision(model); + _shipSystemA.Decision = new ExternalDecision(); _playerSystem = World.Active.GetExistingManager(); _playerSystem.Decision = new HumanDecision(); _playerSystem.SetNewComponentGroup(typeof(PlayerFlag)); @@ -60,6 +61,8 @@ namespace ECS_MLAgents_v0.Example.SpaceWars.Scripts ReloadTime = 1f, MaxReloadTime = 1f }); + + Spawn(100); } diff --git a/Assets/python_communication.py b/Assets/python_communication.py new file mode 100644 index 0000000..20c60b5 --- /dev/null +++ b/Assets/python_communication.py @@ -0,0 +1,92 @@ +import mmap +import struct +import numpy as np +import time + +class UnityCommunication: + FILE_CAPACITY = 200000 + NUMBER_AGENTS_POSITION = 0 + SENSOR_SIZE_POSITION = 4 + ACTUATOR_SIZE_POSITION = 8 + UNITY_READY_POSITION = 12 + SENSOR_DATA_POSITION = 13 + + PYTHON_READY_POSITION = 100000 + ACTUATOR_DATA_POSITION = 100001 + + FILE_NAME = "shared_communication_file.txt" + + def __init__(self): + with open(self.FILE_NAME, "r+b") as f: + # memory-map the file, size 0 means whole file + self.accessor = mmap.mmap(f.fileno(), 0) + + def get_int(self, position : int) -> int: + return struct.unpack("i", self.accessor[position:position + 4])[0] + + def read_sensor(self) -> np.ndarray: + sensor_size = self.get_int(self.SENSOR_SIZE_POSITION) + number_agents = self.get_int(self.NUMBER_AGENTS_POSITION) + + sensor = np.frombuffer( + buffer=self.accessor[self.SENSOR_DATA_POSITION: self.SENSOR_DATA_POSITION + 4*sensor_size*number_agents], + dtype=np.float32, + count=sensor_size * number_agents, + offset=0 + ) + return np.reshape(sensor, (number_agents, sensor_size)) + + def get_parameters(self) -> (int, int, int): + return self.get_int(self.NUMBER_AGENTS_POSITION), \ + self.get_int(self.SENSOR_SIZE_POSITION), \ + self.get_int(self.ACTUATOR_SIZE_POSITION) + + def write_actuator(self, actuator: np.ndarray): + actuator_size = self.get_int(self.ACTUATOR_SIZE_POSITION) + number_agents = self.get_int(self.NUMBER_AGENTS_POSITION) + + # TODO : Support more types ? + if actuator.dtype != np.float32: + actuator = actuator.astype(np.float32) + + assert(actuator.shape == (number_agents, actuator_size)) + + self.accessor[self.ACTUATOR_DATA_POSITION: self.ACTUATOR_DATA_POSITION + 4*actuator_size*number_agents] = \ + actuator.tobytes() + + def set_ready(self, flag : bool): + self.accessor[self.PYTHON_READY_POSITION: self.PYTHON_READY_POSITION+1] = bytearray(struct.pack("b", flag)) + + def unity_ready(self) -> bool: + return self.accessor[self.UNITY_READY_POSITION] + + def close(self): + self.accessor.close() + + +if __name__ == "__main__": + comm = UnityCommunication() + + steps = 0 + while True: + + u_ready = False + while not u_ready: + u_ready = comm.unity_ready() + steps += 1 + s = comm.read_sensor() + nag, nse, nac = comm.get_parameters() + time.sleep(0.1) + comm.write_actuator( + np.random.normal(size=(nag, nac)) + ) + comm.set_ready(True) + + + + + + + + + diff --git a/Assets/python_communication.py.meta b/Assets/python_communication.py.meta new file mode 100644 index 0000000..e8a8d2c --- /dev/null +++ b/Assets/python_communication.py.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 2f0ad029c108747edacf337bce7c2b95 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/shared_communication_file.txt b/Assets/shared_communication_file.txt new file mode 100755 index 0000000..06f7a91 Binary files /dev/null and b/Assets/shared_communication_file.txt differ diff --git a/Assets/shared_communication_file.txt.meta b/Assets/shared_communication_file.txt.meta new file mode 100644 index 0000000..3d0e143 --- /dev/null +++ b/Assets/shared_communication_file.txt.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: f0ca7a0f2c172427f92e5a877c40c2f9 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: