Neodroid  0.2.0
Machine Learning Environment Prototyping Tool
MessageServer.cs
Go to the documentation of this file.
1 using System;
2 using System.Linq;
3 using System.Threading;
4 using AsyncIO;
7 using FlatBuffers;
8 using NetMQ;
9 using NetMQ.Sockets;
10 using UnityEngine;
11 
12 namespace droid.Runtime.Messaging {
15  [Serializable]
16  public class MessageServer {
17  #region PublicMembers
18 
22 
23  #endregion
24 
25  #region PrivateMembers
26 
29  Thread _polling_thread;
30  #if NEODROID_DEBUG
31  int _last_send_frame_number;
32 
33  float _last_send_time;
34  #endif
35 
38  Thread _wait_for_client_thread;
39 
42  object _stop_lock = new object();
43 
44  object _thread_lock = new object();
45 
48  bool _stop_thread;
49 
52  bool _waiting_for_main_loop_to_send;
53 
56  bool _use_inter_process_communication;
57 
60  bool _debugging;
61 
64  ResponseSocket _socket;
65 
66  //PairSocket _socket;
69  string _ip_address;
70 
73  int _port;
74 
77  byte[] _byte_buffer;
78 
81  Double _wait_time_seconds;
82 
83  const string _api_version = "0.1.2";
84 
85  #endregion
86 
87  #region PrivateMethods
88 
89  #region Threads
90 
95  void BindSocket(Action callback, Action<String> debug_callback) {
96  if (this._debugging) {
97  debug_callback?.Invoke("Start listening for clients");
98  }
99 
100  try {
101  if (this._use_inter_process_communication) {
102  this._socket.Bind("ipc:///tmp/neodroid/messages");
103  } else {
104  this._socket.Bind("tcp://" + this._ip_address + ":" + this._port);
105  }
106 
107  callback?.Invoke();
108  if (this._debugging) {
109  debug_callback?.Invoke("Now listening for clients");
110  }
111 
112  this._Listening_For_Clients = true;
113  } catch (Exception exception) {
114  if (this._debugging) {
115  debug_callback?.Invoke($"BindSocket threw exception: {exception}");
116  }
117  }
118  }
119 
124  public Reaction[] Receive(TimeSpan wait_time) {
125  //this._socket.Poll(); // TODO: MAYBE WAIT FOR CLIENT TO SEND
126 
127  Reaction[] reactions = null;
128  lock (this._thread_lock) {
129  try {
130  byte[] msg;
131 
132  if (wait_time > TimeSpan.Zero) {
133  #if NEODROID_DEBUG
134  var received = this._socket.TryReceiveFrameBytes(wait_time, out msg);
135  if (this.Debugging) {
136  if (received) {
137  Debug.Log("Received frame bytes");
138  } else {
139  Debug.Log($"Received nothing in {wait_time} seconds");
140  }
141  }
142  #else
143  this._socket.TryReceiveFrameBytes(wait_time, out msg);
144  #endif
145  } else {
146  try {
147  msg = this._socket.ReceiveFrameBytes();
148  } catch (ArgumentNullException e) {
149  msg = null;
150  Debug.Log(e);
151  }
152  }
153 
154  if (msg != null) { //&& msg.Length >= 4) {
155  var flat_reaction = FReactions.GetRootAsFReactions(new ByteBuffer(msg));
156  var tuple = FbsReactionUtilities.deserialise_reactions(flat_reaction);
157  reactions = tuple.Item1; //TODO: Change tuple to the Reactions class
158  var close = tuple.Item2;
159  var api_version = tuple.Item3;
160  var simulator_configuration = tuple.Item4;
161  }
162  } catch (Exception exception) {
163  if (exception is TerminatingException) {
164  return reactions;
165  }
166 
167  Debug.Log(exception);
168  }
169  }
170 
171  return reactions;
172  }
173 
179  void PollingThread(Action<Reaction[]> receive_callback,
180  Action disconnect_callback,
181  Action<string> debug_callback) {
182  while (this._stop_thread == false) {
183  lock (this._thread_lock) {
184  if (!this._waiting_for_main_loop_to_send) {
185  var reactions = this.Receive(TimeSpan.FromSeconds(this._wait_time_seconds));
186  if (reactions != null) {
187  receive_callback(reactions);
188  this._waiting_for_main_loop_to_send = true;
189  }
190  } else {
191  if (this._debugging) {
192  debug_callback("Waiting for main loop to send reply");
193  }
194  }
195  }
196  }
197 
198  disconnect_callback();
199  if (!this._socket.IsDisposed) {
200  if (this._use_inter_process_communication) {
201  this._socket.Disconnect("inproc://neodroid");
202  } else {
203  this._socket.Disconnect("tcp://" + this._ip_address + ":" + this._port);
204  }
205  }
206 
207  try {
208  this._socket.Dispose();
209  this._socket.Close();
210  } finally {
211  NetMQConfig.Cleanup(false);
212  }
213  }
214 
215  #endregion
216 
217  #endregion
218 
219  #region PublicMethods
220 
229  public void SendStates(EnvironmentState[] environment_states,
230  bool do_serialise_unobservables = false,
231  bool serialise_individual_observables = false,
232  bool serialise_aggregated_float_array = false,
233  SimulatorConfigurationMessage simulator_configuration_message = null,
234  string api_version = _api_version) {
235  lock (this._thread_lock) {
236  #if NEODROID_DEBUG
237  if (this.Debugging) {
238  var environment_state = environment_states.ToArray();
239  if (environment_state.Length > 0) {
240  if (environment_state[0] != null) {
241  var frame_number = environment_state[0].FrameNumber;
242  var time = environment_state[0].Time;
243  var frame_number_duplicate = this._last_send_frame_number == frame_number;
244  if (frame_number_duplicate && frame_number > 0) {
245  Debug.LogWarning($"Sending duplicate frame! Frame number: {frame_number}");
246  }
247 
248  if (frame_number <= this._last_send_frame_number) {
249  Debug.LogWarning($"The current frame number {frame_number} is less or equal the last {this._last_send_frame_number}, SINCE AWAKE ({Time.frameCount})");
250  }
251 
252  if (time <= this._last_send_time) {
253  Debug.LogWarning($"The current time {time} is less or equal the last {this._last_send_time}");
254  }
255 
256  if (environment_state[0].Description != null) {
257  Debug.Log($"State has description: {environment_state[0].Description}");
258  }
259 
260  this._last_send_frame_number = frame_number;
261  this._last_send_time = time;
262  }
263  } else {
264  Debug.LogWarning("No environment states where send.");
265  }
266  }
267  #endif
268 
269  this._byte_buffer = FbsStateUtilities.Serialise(environment_states,
270  do_serialise_unobservables :
271  do_serialise_unobservables,
272  serialise_individual_observables :
273  serialise_individual_observables,
274  simulator_configuration :
275  simulator_configuration_message,
276  do_serialise_aggregated_float_array :
277  serialise_aggregated_float_array,
278  api_version : api_version);
279  this._socket.SendFrame(this._byte_buffer);
280  this._waiting_for_main_loop_to_send = false;
281  }
282  }
283 
287  public void ListenForClientToConnect(Action<string> debug_callback) {
288  this.BindSocket(null, debug_callback);
289  }
290 
295  public void ListenForClientToConnect(Action callback, Action<string> debug_callback) {
296  this._wait_for_client_thread =
297  new Thread(unused_param => this.BindSocket(callback, debug_callback)) {IsBackground = true};
298  // Is terminated with foreground threads, when they terminate
299  this._wait_for_client_thread.Start();
300  }
301 
307  public void StartReceiving(Action<Reaction[]> cmd_callback,
308  Action disconnect_callback,
309  Action<string> debug_callback) {
310  this._polling_thread =
311  new Thread(unused_param => this.PollingThread(cmd_callback, disconnect_callback, debug_callback)) {
312  IsBackground
313  = true
314  };
315  // Is terminated with foreground threads, when they terminate
316  this._polling_thread.Start();
317  }
318 
319  #region Contstruction
320 
321  public MessageServer(string ip_address = "127.0.0.1",
322  int port = 6969,
323  bool use_inter_process_communication = false,
324  bool debug = false,
325  Double wait_time_seconds = 2) {
326  this._wait_time_seconds = wait_time_seconds;
327  this.Debugging = debug;
328  this._ip_address = ip_address;
329  this._port = port;
330  this._use_inter_process_communication = use_inter_process_communication;
331 
332  #if NEODROID_DEBUG
333  if (this.Debugging) {
334  Debug.Log($"Starting a message server at address:port {ip_address}:{port }");
335  }
336  #endif
337 
338  if (!this._use_inter_process_communication) {
339  ForceDotNet.Force();
340  }
341 
342  this._socket = new ResponseSocket();
343  }
344 
345  public MessageServer(bool debug = false) : this("127.0.0.1", 6969, false, debug) { }
346 
347  #endregion
348 
349  #region Getters
350 
353  public bool Debugging { get { return this._debugging; } set { this._debugging = value; } }
354 
355  #endregion
356 
357  #endregion
358 
359  #region Deconstruction
360 
363  public void Destroy() { this.CleanUp(); }
364 
367  public void CleanUp() {
368  try {
369  lock (this._stop_lock) {
370  this._stop_thread = true;
371  }
372 
373  if (this._use_inter_process_communication) {
374  this._socket.Disconnect("ipc:///tmp/neodroid/messages");
375  } else {
376  this._socket.Disconnect("tcp://" + this._ip_address + ":" + this._port);
377  }
378 
379  try {
380  this._socket.Dispose();
381  this._socket.Close();
382  } finally {
383  NetMQConfig.Cleanup(false);
384  }
385 
386  this._wait_for_client_thread?.Join();
387  this._polling_thread?.Join();
388  } catch {
389  Console.WriteLine("Exception thrown while killing threads");
390  }
391  }
392 
393  #endregion
394  }
395 }
void SendStates(EnvironmentState[] environment_states, bool do_serialise_unobservables=false, bool serialise_individual_observables=false, bool serialise_aggregated_float_array=false, SimulatorConfigurationMessage simulator_configuration_message=null, string api_version=_api_version)
void ListenForClientToConnect(Action< string > debug_callback)
void StartReceiving(Action< Reaction[]> cmd_callback, Action disconnect_callback, Action< string > debug_callback)
static FReactions GetRootAsFReactions(ByteBuffer _bb)
void ListenForClientToConnect(Action callback, Action< string > debug_callback)
Reaction [] Receive(TimeSpan wait_time)
MessageServer(string ip_address="127.0.0.1", int port=6969, bool use_inter_process_communication=false, bool debug=false, Double wait_time_seconds=2)