Skip to content

Commit 2db736c

Browse files
Merge pull request #1 from Unity-Technologies/experiment-shared-memory
Experiment shared memory
2 parents 4e9e3af + 8b87931 commit 2db736c

13 files changed

+547
-9
lines changed

Assets/ECSEnvironment.py

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
import mmap
2+
import struct
3+
import numpy as np
4+
5+
from .brain import BrainInfo, BrainParameters
6+
7+
8+
class ECSEnvironment(object):
9+
VEC_SIZE = 8
10+
ACT_SIZE = 3
11+
12+
def __init__(self):
13+
self.comm = UnityCommunication()
14+
self.step_count = 0
15+
print(" READY TO COMMUNICATE")
16+
17+
def reset(self, train_mode=True, config=None, lesson=None):
18+
u_ready = False
19+
while not u_ready:
20+
u_ready = self.comm.unity_ready()
21+
s = self.comm.read_sensor()
22+
return self.make_brain_info(s)
23+
24+
def step(self, vector_action=None, memory=None, text_action=None, value=None):
25+
# print("step")
26+
self.comm.write_actuator(vector_action["ECSBrain"])
27+
self.comm.set_ready()
28+
29+
u_ready = False
30+
while not u_ready:
31+
u_ready = self.comm.unity_ready()
32+
s = self.comm.read_sensor()
33+
return self.make_brain_info(s)
34+
35+
def close(self):
36+
self.comm.close()
37+
38+
def make_brain_info(self, sensor):
39+
# This assumes the order is consistent
40+
self.step_count+=1
41+
done = False
42+
if self.step_count % 50 == 0:
43+
done = True
44+
return {"ECSBrain" : BrainInfo([], sensor, [" "] * sensor.shape[0],
45+
reward=sensor[:,0],
46+
agents=list(range(sensor.shape[0])),
47+
local_done=[done] * sensor.shape[0],
48+
max_reached=[done] * sensor.shape[0],
49+
vector_action=sensor,
50+
text_action = [" "] * sensor.shape[0])}
51+
52+
53+
@property
54+
def curriculum(self):
55+
return None
56+
57+
@property
58+
def logfile_path(self):
59+
return None
60+
61+
@property
62+
def brains(self):
63+
return {"ECSBrain": BrainParameters("ECSBrain", self.VEC_SIZE, 1,
64+
[], [self.ACT_SIZE],
65+
[" "]*self.ACT_SIZE, 1)} # 1 for continuopus
66+
67+
@property
68+
def global_done(self):
69+
return False
70+
71+
@property
72+
def academy_name(self):
73+
return "ECSAcademy"
74+
75+
@property
76+
def number_brains(self):
77+
return 1
78+
79+
@property
80+
def number_external_brains(self):
81+
return 1
82+
83+
@property
84+
def brain_names(self):
85+
return ["ECSBrain"]
86+
87+
@property
88+
def external_brain_names(self):
89+
return ["ECSBrain"]
90+
91+
92+
93+
94+
95+
class UnityCommunication:
96+
FILE_CAPACITY = 200000
97+
NUMBER_AGENTS_POSITION = 0
98+
SENSOR_SIZE_POSITION = 4
99+
ACTUATOR_SIZE_POSITION = 8
100+
UNITY_READY_POSITION = 12
101+
SENSOR_DATA_POSITION = 13
102+
103+
PYTHON_READY_POSITION = 100000
104+
ACTUATOR_DATA_POSITION = 100001
105+
106+
# FILE_NAME = "../../../ml-agents-ecs/Assets/shared_communication_file.txt"
107+
FILE_NAME = "shared_communication_file.txt" # This is relative to where the script is called
108+
109+
def __init__(self):
110+
with open(self.FILE_NAME, "r+b") as f:
111+
# memory-map the file, size 0 means whole file
112+
self.accessor = mmap.mmap(f.fileno(), 0)
113+
114+
def get_int(self, position : int) -> int:
115+
return struct.unpack("i", self.accessor[position:position + 4])[0]
116+
117+
def read_sensor(self) -> np.ndarray:
118+
sensor_size = self.get_int(self.SENSOR_SIZE_POSITION)
119+
number_agents = self.get_int(self.NUMBER_AGENTS_POSITION)
120+
121+
sensor = np.frombuffer(
122+
buffer=self.accessor[self.SENSOR_DATA_POSITION: self.SENSOR_DATA_POSITION + 4*sensor_size*number_agents],
123+
dtype=np.float32,
124+
count=sensor_size * number_agents,
125+
offset=0
126+
)
127+
return np.reshape(sensor, (number_agents, sensor_size))
128+
129+
def get_parameters(self) -> (int, int, int):
130+
return self.get_int(self.NUMBER_AGENTS_POSITION), \
131+
self.get_int(self.SENSOR_SIZE_POSITION), \
132+
self.get_int(self.ACTUATOR_SIZE_POSITION)
133+
134+
def write_actuator(self, actuator: np.ndarray):
135+
actuator_size = self.get_int(self.ACTUATOR_SIZE_POSITION)
136+
number_agents = self.get_int(self.NUMBER_AGENTS_POSITION)
137+
138+
# TODO : Support more types ?
139+
if actuator.dtype != np.float32:
140+
actuator = actuator.astype(np.float32)
141+
142+
try:
143+
assert(actuator.shape == (number_agents, actuator_size))
144+
except:
145+
print("_________")
146+
print(actuator.shape)
147+
print((number_agents, actuator_size))
148+
149+
self.accessor[self.ACTUATOR_DATA_POSITION: self.ACTUATOR_DATA_POSITION + 4*actuator_size*number_agents] = \
150+
actuator.tobytes()
151+
152+
def set_ready(self):
153+
self.accessor[self.UNITY_READY_POSITION: self.UNITY_READY_POSITION+1] = bytearray(struct.pack("b", False))
154+
self.accessor[self.PYTHON_READY_POSITION: self.PYTHON_READY_POSITION+1] = bytearray(struct.pack("b", True))
155+
156+
def unity_ready(self) -> bool:
157+
return self.accessor[self.UNITY_READY_POSITION]
158+
159+
def close(self):
160+
self.accessor.close()
161+
162+
163+
# if __name__ == "__main__":
164+
# comm = UnityCommunication()
165+
#
166+
# steps = 0
167+
# while True:
168+
#
169+
# u_ready = False
170+
# while not u_ready:
171+
# u_ready = comm.unity_ready()
172+
# steps += 1
173+
# s = comm.read_sensor()
174+
# nag, nse, nac = comm.get_parameters()
175+
# # print(s.shape)
176+
# # time.sleep(0.1)
177+
# comm.write_actuator(
178+
# np.random.normal(size=(nag, nac))
179+
# )
180+
# comm.set_ready()
181+
#
182+
183+

Assets/ECS_MLAgents_v0/Core/AgentSystem.cs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Linq;
1+
using System.Diagnostics.CodeAnalysis;
2+
using System.Linq;
23
using Unity.Collections;
34
using Unity.Collections.LowLevel.Unsafe;
45
using Unity.Entities;
@@ -44,7 +45,7 @@ public abstract class AgentSystem<TS, TA> : JobComponentSystem, IAgentSystem
4445
private int _sensorMemorySize = INITIAL_MEMORY_SIZE;
4546
private int _actuatorMemorySize = INITIAL_MEMORY_SIZE;
4647

47-
public int DecisionInterval { get; set; }
48+
public IDecisionRequester DecisionRequester { get; set; }
4849
private int _phase;
4950

5051
public IAgentDecision Decision { get; set; }
@@ -63,6 +64,11 @@ public abstract class AgentSystem<TS, TA> : JobComponentSystem, IAgentSystem
6364

6465
protected override void OnCreateManager()
6566
{
67+
if (DecisionRequester == null)
68+
{
69+
DecisionRequester = new FixedCountRequester();
70+
}
71+
6672
_logger = new Logger(GetType().Name);
6773
_logger.Log("OnCreateManager");
6874
SetNewComponentGroup();
@@ -108,12 +114,13 @@ protected override JobHandle OnUpdate(JobHandle inputDeps)
108114
{
109115
_logger.Log("OnUpdate");
110116

111-
if (_phase > 0)
117+
DecisionRequester.Update();
118+
if (!DecisionRequester.Ready)
112119
{
113-
_phase--;
114120
return inputDeps;
115121
}
116-
_phase = DecisionInterval;
122+
DecisionRequester.Reset();
123+
117124

118125
var nAgents = _componentGroup.CalculateLength();
119126

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
using System;
2+
using Unity.Collections;
3+
using Unity.Jobs;
4+
using System.IO.MemoryMappedFiles;
5+
using System.IO;
6+
using Unity.Collections.LowLevel.Unsafe;
7+
using UnityEngine;
8+
using UnityEngine.Profiling;
9+
10+
11+
namespace ECS_MLAgents_v0.Core{
12+
public class ExternalDecision : IAgentDecision
13+
{
14+
15+
// [ Unity Ready (1) , nAgents (4) , sensorSize (4) , actuatorSize (4) , Data
16+
17+
// TODO : This capacity needs to scale / communicate multiple times per step ?
18+
private const int FILE_CAPACITY = 200000;
19+
private const int NUMBER_AGENTS_POSITION = 0;
20+
private const int SENSOR_SIZE_POSITION = 4;
21+
private const int ACTUATOR_SIZE_POSITION = 8;
22+
private const int UNITY_READY_POSITION = 12;
23+
private const int SENSOR_DATA_POSITION = 13;
24+
25+
private const int PYTHON_READY_POSITION = 100000;
26+
private const int ACTUATOR_DATA_POSITION = 100001;
27+
28+
29+
private float[] actuatorData = new float[0];
30+
31+
// This is a temporary test file
32+
// TODO : Replace with a file creation system
33+
// TODO : Implement the communication in a separate class
34+
// TODO : Have separate files for sensor and actuators
35+
private string filenameWrite = "Assets/shared_communication_file.txt";
36+
37+
private MemoryMappedViewAccessor accessor;
38+
39+
public ExternalDecision()
40+
{
41+
var mmf = MemoryMappedFile.CreateFromFile(filenameWrite, FileMode.Open, "Test");
42+
accessor = mmf.CreateViewAccessor(
43+
0, FILE_CAPACITY, MemoryMappedFileAccess.ReadWrite);
44+
// accessor.WriteArray(0, new bool[FILE_CAPACITY], 0, FILE_CAPACITY);
45+
accessor.Write(PYTHON_READY_POSITION, false);
46+
accessor.Write(UNITY_READY_POSITION, false);
47+
Debug.Log("Is Ready to Communicate");
48+
}
49+
50+
public JobHandle DecideBatch(
51+
ref NativeArray<float> sensor,
52+
ref NativeArray<float> actuator,
53+
int sensorSize,
54+
int actuatorSize,
55+
int nAgents,
56+
JobHandle handle)
57+
{
58+
Profiler.BeginSample("Communicating");
59+
if (sensor.Length > 4 * 50000)
60+
{
61+
throw new Exception("TOO much data to send");
62+
}
63+
64+
if (actuator.Length > 4 * 50000)
65+
{
66+
throw new Exception("TOO much data to send");
67+
}
68+
69+
if (actuatorData.Length < actuator.Length)
70+
{
71+
actuatorData = new float[actuator.Length];
72+
}
73+
74+
75+
accessor.Write(NUMBER_AGENTS_POSITION, nAgents);
76+
accessor.Write(SENSOR_SIZE_POSITION, sensorSize);
77+
accessor.Write(ACTUATOR_SIZE_POSITION, actuatorSize);
78+
79+
accessor.WriteArray(SENSOR_DATA_POSITION, sensor.ToArray(), 0, sensor.Length);
80+
81+
accessor.Write(PYTHON_READY_POSITION, false);
82+
83+
accessor.Write(UNITY_READY_POSITION, true);
84+
85+
86+
var readyToContinue = false;
87+
int loopIter = 0;
88+
while (!readyToContinue)
89+
{
90+
loopIter++;
91+
readyToContinue = accessor.ReadBoolean(PYTHON_READY_POSITION);
92+
readyToContinue = readyToContinue || loopIter > 20000000;
93+
if (loopIter > 20000000)
94+
{
95+
Debug.Log("Missed Communication");
96+
}
97+
}
98+
99+
accessor.ReadArray(ACTUATOR_DATA_POSITION, actuatorData, 0, actuator.Length);
100+
actuator.CopyFrom(actuatorData);
101+
102+
Profiler.BeginSample("Communicating");
103+
return handle;
104+
}
105+
}
106+
}

Assets/ECS_MLAgents_v0/Core/ExternalDecision.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Assets/ECS_MLAgents_v0/Core/IAgentSystem.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,6 @@ void SetFilter<T0, T1>(T0 filterA, T1 filterB)
4646
/// </summary>
4747
void ResetFilter();
4848

49-
int DecisionInterval { get; set; }
49+
IDecisionRequester DecisionRequester { get; set; }
5050
}
5151
}

0 commit comments

Comments
 (0)