simulation_client.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. '''
  2. TODO:
  3. V Realtime interrupts of the visualization need to be propagated to the simulation.
  4. V Change visualization to show in same browser window, not a pop-up.
  5. V Reset
  6. V Communication debugging interface -> visualization interface (for reset for example, or step back).
  7. V State visualization in debugging interface (on hover?).
  8. V Communication visualization interface -> debugging interface (for 'select an entity', for example).
  9. V As fast as possible simulation.
  10. V Visualization of "current small step" (1-8).
  11. V Breakpoints.
  12. V Inject events.
  13. V God events.
  14. V Fix bug: no visualization when small stepping.
  15. V Highlight transitioning nodes.
  16. V Display simulation time.
  17. V Realtime scale.
  18. V Termination condition.
  19. V Handling of multiple connections for a single output port when a message is routed.
  20. - Remove and add element/port/port connection. (? validity)
  21. - Proper serialization of state: now I only serialize primitive values, and ignore composite ones. Needs to have a translation between Python and JS.
  22. - Parameters for simulation initialization.
  23. - Robustness.
  24. - Each client has their own debugger?
  25. - Integrate with new version of SCCD (see Joeri's mail).
  26. '''
  27. import socket, sys, json, threading, time, pickle, copy
  28. sys.path.append("pypdevs")
  29. sys.path.append("..")
  30. import Tkinter as tk
  31. from particle_interaction import *
  32. import sccd
  33. from python_runtime.statecharts_core import Event
  34. HOST, PORT = "127.0.0.1", 9999
  35. sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  36. sock.connect((HOST, PORT))
  37. '''
  38. This function actually receives messages from the debugging interface (or instrumented visualizatin interface).
  39. The communication protocol is very basic: all messages are terminated with a new line. This function makes sure
  40. only 'full' messages are returned.
  41. '''
  42. end='\n'
  43. def recv_end(the_socket):
  44. total_data = []
  45. data = ''
  46. while True:
  47. data = the_socket.recv(8192)
  48. if end in data:
  49. total_data.append(data[:data.find(end)])
  50. break
  51. total_data.append(data)
  52. if len(total_data) > 1:
  53. last_pair = total_data[-2] + total_data[-1]
  54. if end in last_pair:
  55. total_data[-2]=last_pair[:last_pair.find(end)]
  56. total_data.pop()
  57. break
  58. return ''.join(total_data)
  59. '''
  60. Send a message to the communication layer (which will then be routed to either the debugging interface or the
  61. visualization server, based on the first entry in the sent array). Each message needs to end with a new line,
  62. as the protocol specifies.
  63. '''
  64. def send_msg(msg):
  65. try:
  66. sock.send(msg + '\n')
  67. except socket.error:
  68. sys.exit(0)
  69. '''
  70. Handle output messages of the simulation on the pos, color, and time ports, respectively.
  71. '''
  72. def handle_output_pos(msgs):
  73. for msg_contents in msgs:
  74. send_msg(json.dumps(["VIS", "POS_OUT", msg_contents]))
  75. def handle_output_color(msgs):
  76. for msg_contents in msgs:
  77. send_msg(json.dumps(["VIS", "COLOR_OUT", msg_contents]))
  78. def handle_output_time(msgs):
  79. for msg_contents in msgs:
  80. send_msg(json.dumps(["VIS", "TIME_OUT", "%.2f" % (float(msg_contents / 30.0))]))
  81. '''
  82. Serializes a Python object to a form where it is ready for JSON serialization.
  83. '''
  84. def todict(obj, classkey=None):
  85. if isinstance(obj, dict):
  86. data = {}
  87. for (k, v) in obj.items():
  88. if v.__class__.__name__.endswith("State") or not hasattr(v, '__dict__'): # TODO: UGLY!!!
  89. data[k] = todict(v, classkey)
  90. return data
  91. elif hasattr(obj, "__iter__"):
  92. return [todict(v, classkey) for v in copy.copy(obj) if (v.__class__.__name__.endswith("State") or not hasattr(v, '__dict__'))] # TODO: UGLY!!!
  93. elif hasattr(obj, "__dict__"):
  94. data = dict([(key, todict(value, classkey))
  95. for key, value in copy.copy(obj.__dict__).iteritems()
  96. if not callable(value) and not key.startswith('_') and (value.__class__.__name__.endswith("State") or not hasattr(value, '__dict__'))]) # TODO: UGLY!!!
  97. if classkey is not None and hasattr(obj, "__class__"):
  98. data[classkey] = obj.__class__.__name__
  99. return data
  100. else:
  101. return obj
  102. '''
  103. Blocks until a message is received on the specified port. Immediately sends that message (which needs to be
  104. forwarded to the debugging interface).
  105. '''
  106. def poll_port(port):
  107. while 1:
  108. msg = port.fetch(-1)
  109. send_msg(json.dumps(["DBG", todict(msg)]))
  110. if __name__ == '__main__':
  111. # Simulation parameters.
  112. resolution = (800, 600)
  113. framerate = 30
  114. spawn_chance = 0.2
  115. die_chance = 0.01
  116. # Instantiate model to simulate.
  117. model = Root(Canvas(resolution), framerate, spawn_chance, die_chance)
  118. # Instantiate and start our debugger (compiled Statecharts model).
  119. sim_controller = sccd.Controller(model)
  120. sim_controller.start()
  121. # Forward debugging messages to debugging server (in a separate thread).
  122. reply_port = sim_controller.addOutputListener('reply')
  123. thrd = threading.Thread(target=poll_port,args=[reply_port])
  124. thrd.daemon = True
  125. thrd.start()
  126. # Listen for simulation output on specified DEVS ports.
  127. sim_controller.addInput(Event("set_listen_ports", "request", ["POS_OUT", handle_output_pos]))
  128. sim_controller.addInput(Event("set_listen_ports", "request", ["COLOR_OUT", handle_output_color]))
  129. sim_controller.addInput(Event("set_listen_ports", "request", ["TIME_OUT", handle_output_time]))
  130. print 'Waiting for commands...'
  131. # Main loop: receive a message from the debugging interface (or the instrumented visualization server) and send it to the debugger.
  132. while 1:
  133. try:
  134. msg = recv_end(sock)
  135. except socket.error:
  136. break
  137. msg_head, msg_body = msg[:4], msg[4:]
  138. if msg_head == 'dbg_':
  139. decoded_event = json.loads(msg_body);
  140. parameters = decoded_event["parameters"]
  141. if decoded_event["name"] == "god_event":
  142. event = Event("god_event", "request", parameters)
  143. elif decoded_event["name"] == "inject":
  144. event = Event("inject", "request", parameters)
  145. elif decoded_event["name"] == "add_breakpoint":
  146. event = Event("add_breakpoint", "request", parameters[0])
  147. elif decoded_event["name"] == "toggle_breakpoint":
  148. event = Event("toggle_breakpoint", "request", parameters)
  149. elif decoded_event["name"] == "del_breakpoint":
  150. event = Event("del_breakpoint", "request", parameters)
  151. elif decoded_event["name"] == "reset":
  152. event = Event("reset", "request", parameters)
  153. elif parameters[0] == "realtime":
  154. event = Event(parameters[0], "request", [{"termination_condition": termination_condition, "realtime_scale": float(parameters[1]["realtime_scale"])}])
  155. else:
  156. event = Event(parameters[0], "request", [{"termination_condition": termination_condition}])
  157. sim_controller.addInput(event)
  158. elif msg_head == 'vis_':
  159. event = Event("realtime_interrupt", "request", [msg_body])
  160. sim_controller.addInput(event)