def run(self, inputcode, iterations=None, run_forever=False, frame_limiter=False, verbose=False, break_on_error=False): ''' Executes the contents of a Nodebox/Shoebot script in current surface's context. :param inputcode: Path to shoebot source or string containing source :param iterations: None or Maximum amount of frames to run :param run_forever: If True then run until user quits the bot :param frame_limiter: If True then sleep between frames to respect speed() command. ''' source = None filename = None if os.path.isfile(inputcode): source = open(inputcode).read() filename = inputcode elif isinstance(inputcode, str): filename = 'shoebot_code' source = inputcode self._load_namespace(self._namespace, filename) self._executor = executor = LiveExecution(source, ns=self._namespace, filename=filename) try: if not iterations: if run_forever: iterations = None else: iterations = 1 iteration = 0 event = None while iteration != iterations and not event_is(event, QUIT_EVENT): # do the magic # First iteration self._run_frame(executor, limit=frame_limiter, iteration=iteration) if iteration == 0: self._initial_namespace = copy.copy( self._namespace) # Stored so script can be rewound iteration += 1 # Subsequent iterations while self._should_run(iteration, iterations) and event is None: iteration += 1 self._run_frame(executor, limit=frame_limiter, iteration=iteration) event = next_event() if not event: self._canvas.sink.main_iteration( ) # update GUI, may generate events.. while run_forever: # # Running in GUI, bot has finished # Either - # receive quit event and quit # receive any other event and loop (e.g. if var changed or source edited) # while event is None: self._canvas.sink.main_iteration() event = next_event(block=True, timeout=0.05) if not event: self._canvas.sink.main_iteration( ) # update GUI, may generate events.. if event.type == QUIT_EVENT: break elif event.type == SOURCE_CHANGED_EVENT: # Debounce SOURCE_CHANGED events - # gedit generates two events for changing a single character - # delete and then add while event and event.type == SOURCE_CHANGED_EVENT: event = next_event(block=True, timeout=0.001) elif event.type == SET_WINDOW_TITLE: self._canvas.sink.set_title(event.data) event = None # this loop is a bit weird... break except Exception as e: # this makes KeyboardInterrupts still work # if something goes wrong, print verbose system output # maybe this is too verbose, but okay for now import sys if verbose: errmsg = traceback.format_exc() else: errmsg = simple_traceback(e, executor.known_good or '') print(errmsg, file=sys.stderr) if break_on_error: raise
def run(self, inputcode, iterations=None, run_forever=False, frame_limiter=False, verbose=False): ''' Executes the contents of a Nodebox/Shoebot script in current surface's context. :param inputcode: path to shoebot file or whole source code :param iterations: maximum amount of frames to run :param run_forever: if True will run until the user quits the bot :param frame_limiter: Time a frame should take to run (float - seconds) ''' source = None filename = None if os.path.isfile(inputcode): source = open(inputcode).read() filename = inputcode elif isinstance(inputcode, basestring): filename = 'shoebot_code' source = inputcode self._load_namespace(self._namespace, filename) self._executor = executor = LiveExecution(source, ns=self._namespace, filename=filename) try: if not iterations: if run_forever: iterations = None else: iterations = 1 iteration = 0 event = None while iteration != iterations and not event_is(event, QUIT_EVENT): # do the magic # First iteration self._run_frame(executor, limit=frame_limiter, iteration=iteration) if iteration == 0: self._initial_namespace = copy.copy(self._namespace) # Stored so script can be rewound iteration += 1 # Subsequent iterations while self._should_run(iteration, iterations) and event is None: iteration += 1 self._run_frame(executor, limit=frame_limiter, iteration=iteration) event = next_event() if not event: self._canvas.sink.main_iteration() # update GUI, may generate events.. while run_forever: # # Running in GUI, bot has finished # Either - # receive quit event and quit # receive any other event and loop (e.g. if var changed or source edited) # while event is None: self._canvas.sink.main_iteration() event = next_event(block=True, timeout=0.05) if not event: self._canvas.sink.main_iteration() # update GUI, may generate events.. if event.type == QUIT_EVENT: break elif event.type == SOURCE_CHANGED_EVENT: # Debounce SOURCE_CHANGED events - # gedit generates two events for changing a single character - # delete and then add while event and event.type == SOURCE_CHANGED_EVENT: event = next_event(block=True, timeout=0.001) elif event.type == SET_WINDOW_TITLE: self._canvas.sink.set_title(event.data) event = None # this loop is a bit weird... break except Exception as e: # this makes KeyboardInterrupts still work # if something goes wrong, print verbose system output # maybe this is too verbose, but okay for now import sys if verbose: errmsg = traceback.format_exc() else: errmsg = self._simple_traceback(e, executor.known_good or '') print >> sys.stderr, errmsg
def _handle_events(self, iteration, is_animation, next_frame_due): """ The Shoebot mainloop, GUI and shell communicate with each other using events. Examples include live variables being changed from the GUI, the shell or Shoebot itself, or the user quitting from the GUI. This handler waits for events and updates where needed, the loop also serves handles the delay between frames for animated bots. return: continue_running, restart """ # Things we might want to do on returning: # Restart (if state has changed and not an animation). # Quit # Continue running. restart_bot = False while True: timeout = min(next_frame_due - time(), 0.1) event = next_event( block=timeout > 0, timeout=timeout if timeout > 0 else None ) # Update GUI, which may in-turn generate new events. self._canvas.sink.main_iteration() if event is not None: if event.type == QUIT_EVENT: # The user chose to quit via the shell or GUI. return False, False elif event.type == REDRAW_EVENT: # The GUI needs redrawing (usually because the Window was resized) # TODO: This is a hack/workaround, since the graphics backend doesn't currently support redrawing if not is_animation: return True, True elif event.type == SET_WINDOW_TITLE_EVENT: # A new window title was specified in the shell self._canvas.sink.set_title(event.data) elif event.type == SOURCE_CHANGED_EVENT: # New source code was loaded from the shell. # Debounce SOURCE_CHANGED events - # Gedit generates two events for changing a single character - # delete and then add while event and event.type == SOURCE_CHANGED_EVENT: # TODO, can this be handled differently (non-blocking or just ignore source that is the same?) event = next_event(block=True, timeout=0.001) if not is_animation: return True, True elif event.type == VARIABLE_CHANGED_EVENT: # A Variable was changed, from the shell or the GUI. # TODO, make VARIABLE_ADDED_EVENT, VARIABLE_DELETED_EVENT # TODO, sketched out, fix up properly. self._executor.ns[event.data.name] = event.data.value # TODO: State was updated, bot needs to execute again ??? if not is_animation: # On non-animated bots, updating variables re-runs the whole # whole bot so that the user may see the updated state. return True, True if time() >= next_frame_due: break if event is None: # event is None indicates the handler timed out. # If the bot is animated, then the next frame is due and # variables that update per-frame must be updated. if is_animation and self._speed is not None: if self._speed > 0: self._frame += 1 elif self._speed < 0: self._frame -= 1 self._update_animation_variables(iteration, self._frame) # By default return continue_running=True. return True, restart_bot