''' TODO: V Realtime interrupts of the visualization need to be propagated to the simulation. V Change visualization to show in same browser window, not a pop-up. V Reset V Communication debugging interface -> visualization interface (for reset for example, or step back). V State visualization in debugging interface (on hover?). V Communication visualization interface -> debugging interface (for 'select an entity', for example). V As fast as possible simulation. V Visualization of "current small step" (1-8). V Breakpoints. V Inject events. V God events. V Fix bug: no visualization when small stepping. V Highlight transitioning nodes. V Display simulation time. V Realtime scale. V Termination condition. V Handling of multiple connections for a single output port when a message is routed. - Remove and add element/port/port connection. (? validity) - Proper serialization of state: now I only serialize primitive values, and ignore composite ones. Needs to have a translation between Python and JS. - Parameters for simulation initialization. - Robustness. - Each client has their own debugger? - Integrate with new version of SCCD (see Joeri's mail). ''' import socket, sys, json, threading, time, pickle, copy, os os.chdir("NetLogo") sys.path.append(".") import simulator from sccd.runtime.statecharts_core import Event HOST, PORT = "127.0.0.1", 9999 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((HOST, PORT)) ''' This function actually receives messages from the debugging interface (or instrumented visualizatin interface). The communication protocol is very basic: all messages are terminated with a new line. This function makes sure only 'full' messages are returned. ''' end='\n' def recv_end(the_socket): total_data = [] data = '' while True: data = the_socket.recv(8192) if end in data: total_data.append(data[:data.find(end)]) break total_data.append(data) if len(total_data) > 1: last_pair = total_data[-2] + total_data[-1] if end in last_pair: total_data[-2]=last_pair[:last_pair.find(end)] total_data.pop() break return ''.join(total_data) ''' Send a message to the communication layer (which will then be routed to either the debugging interface or the visualization server, based on the first entry in the sent array). Each message needs to end with a new line, as the protocol specifies. ''' def send_msg(msg): try: sock.send(msg + '\n') except socket.error: sys.exit(0) ''' Handle output messages of the simulation on the pos, color, and time ports, respectively. def handle_output_pos(msgs): for msg_contents in msgs: send_msg(json.dumps(["VIS", "POS_OUT", msg_contents])) def handle_output_color(msgs): for msg_contents in msgs: send_msg(json.dumps(["VIS", "COLOR_OUT", msg_contents])) def handle_output_time(msgs): for msg_contents in msgs: send_msg(json.dumps(["VIS", "TIME_OUT", "%.2f" % (float(msg_contents / 30.0))])) ''' ''' Serializes a Python object to a form where it is ready for JSON serialization. ''' def todict(obj, classkey=None): if isinstance(obj, dict): data = {} for (k, v) in obj.items(): if v.__class__.__name__.endswith("State") or not hasattr(v, '__dict__'): # TODO: UGLY!!! data[k] = todict(v, classkey) return data elif hasattr(obj, "__iter__"): return [todict(v, classkey) for v in copy.copy(obj) if (v.__class__.__name__.endswith("State") or not hasattr(v, '__dict__'))] # TODO: UGLY!!! elif hasattr(obj, "__dict__"): data = dict([(key, todict(value, classkey)) for key, value in copy.copy(obj.__dict__).iteritems() if not callable(value) and not key.startswith('_') and (value.__class__.__name__.endswith("State") or not hasattr(value, '__dict__'))]) # TODO: UGLY!!! if classkey is not None and hasattr(obj, "__class__"): data[classkey] = obj.__class__.__name__ return data else: return obj ''' Blocks until a message is received on the specified port. Immediately sends that message (which needs to be forwarded to the debugging interface). ''' def poll_port(port): while 1: msg = port.fetch(-1) #print msg send_msg(json.dumps(["DBG", todict(msg)])) def wait_for_dbg_msg(): print 'Waiting for commands...' # Main loop: receive a message from the debugging interface (or the instrumented visualization server) and send it to the debugger. while 1: try: msg = recv_end(sock) except socket.error: break #print msg msg_head, msg_body = msg[:4], msg[4:] if msg_head == 'dbg_': decoded_event = json.loads(msg_body); parameters = decoded_event["parameters"] if decoded_event["name"] == "god_event": event = Event("god_event", "request", parameters) elif decoded_event["name"] == "add_breakpoint": event = Event("add_breakpoint", "request", parameters[0]) elif decoded_event["name"] == "toggle_breakpoint": event = Event("toggle_breakpoint", "request", parameters) elif decoded_event["name"] == "del_breakpoint": event = Event("del_breakpoint", "request", parameters) elif decoded_event["name"] == "reset": event = Event("reset", "request", parameters) elif parameters[0] == "realtime": event = Event(parameters[0], "request", [{"realtime_scale": float(parameters[1]["realtime_scale"])}]) elif parameters[0] == "big_step": event = Event("step", "request", []) elif parameters[0] == "simulate": event = Event("continuous", "request", []) else: event = Event(parameters[0], "request", []) sim_controller.addInput(event) ''' elif msg_head == 'vis_': event = Event("realtime_interrupt", "request", [msg_body]) sim_controller.addInput(event) ''' if __name__ == '__main__': # Simulation parameters. ''' resolution = (800, 600) framerate = 30 spawn_chance = 0.2 die_chance = 0.01 # Instantiate model to simulate. model = Root(Canvas(resolution), framerate, spawn_chance, die_chance) ''' # Instantiate and start our debugger (compiled Statecharts model). sim_controller = simulator.Controller('BouncingBalls.nlogo') # Forward debugging messages to debugging server (in a separate thread). reply_port = sim_controller.addOutputListener('reply') thrd = threading.Thread(target=poll_port,args=[reply_port]) thrd.daemon = True thrd.start() # Listen for simulation output on specified DEVS ports. ''' sim_controller.addInput(Event("set_listen_ports", "request", ["POS_OUT", handle_output_pos])) sim_controller.addInput(Event("set_listen_ports", "request", ["COLOR_OUT", handle_output_color])) sim_controller.addInput(Event("set_listen_ports", "request", ["TIME_OUT", handle_output_time])) ''' thrd = threading.Thread(target=wait_for_dbg_msg) thrd.daemon = True thrd.start() sim_controller.start()