def painter_test2(n, lines, columns, fps = 25000): # test main-thread execution (previewer run as main thread): preview = Preview(title = "Main-thread test without diagram", lines = lines, columns = columns, fps_limit = fps, painter_class = TestPainter) timer = Timer(0.1) preview.interactive(True) preview.start() step = 0 # create tests in different threads while True: step += 1 preview.set_title("Test #" + str(step) + " ADD-del inverse order") painters = [] for i in xrange(0,n): painters.append(preview.add_view(i)) while timer.idle(): if not preview.step(): return preview.set_title("Test #" + str(step) + " add-DEL inverse order") while len(painters) > 0: preview.del_view(painters.pop()) while timer.idle(): if not preview.step(): return preview.set_title("Test #" + str(step) + " ADD-del direct order") painters = [] for i in xrange(0,n): painters.append(preview.add_view(i)) while timer.idle(): if not preview.step(): return preview.set_title("Test #" + str(step) + " add-DEL direct order") for i in xrange(0,n): preview.del_view(painters[i]) while timer.idle(): if not preview.step(): return preview.set_title("Test #" + str(step) + " ADD-del random order") painters = [] for i in xrange(0,n): painters.append(preview.add_view(i)) while timer.idle(): if not preview.step(): return preview.set_title("Test #" + str(step) + " add-DEL random order") while len(painters) > 0: i = random.randint(0, len(painters) - 1) preview.del_view(painters[i]) del painters[i] while timer.idle(): if not preview.step(): return
class Preview(threading.Thread): ''' Preview is a thread object that act as a pygame display control PyGame issue on Windows (and possibly other OS than Linux): In Windows OS, pygame just collects events in a main therad. Then, the application must instatiate the Preview and maint it's controler ''' NONE = -1 STARTING = 0 RUNNING = 1 STOPPING = 2 KILLED = 3 preview_control = None def __init__(self, title = "Grid visualizer", width = PreviewDefaults.width, height = PreviewDefaults.height, lines = 2, columns = 2, fps_limit = 0, painter_class = Painter): # state control self.state = Preview.STARTING self.default_painter_class = painter_class # thread control self.threading = True # Window app control self.width = width self.height = height self.bgcolor = Color.BLACK self.display = pygame.display.set_mode((width + VerticalScroll.BUTTOM_SIZE, height), pygame.HWSURFACE | pygame.DOUBLEBUF) pygame.display.set_caption(title) # diagram selector lines = max([1, int(lines)]) columns = max([1, int(columns)]) self.grid = Grid(self.display, lines, columns) self.mouse_drag = False self.key_pressed = None # timer control (to pause, FPS, keyboard) self.fps_limit = fps_limit self.frame_timer = Timer(0) if self.fps_limit > 0: self.frame_timer.set_delay(1.0 / self.fps_limit) self.key_repeat_delay = 1.0 / 2.0 self.key_repeat_rate = 1.0 / 10.0 self.keyboard_timer = Timer(0) # count number of painters/objects self.count_painters = 0 # map object to painter self.views = dict() # multi-thread control self.lock = threading.Lock() def panel_dimensions(self): ''' Get panel dimensions ''' return self.grid.panel_width, self.grid.panel_height def set_title(self, title): ''' Change app window caption ''' pygame.display.set_caption(title) def add_view(self, view, label='', painter_class = None): ''' Include a diagram in previewer ''' assert view is not None with self.lock: # prevent drawing duplication of same object if view in self.views: return False # create a painter to object if painter_class is None: painter = self.default_painter_class(self.count_painters, view, label) else: painter = painter_class(self.count_painters, view, label) self.count_painters += 1 # make control self.grid.add_painter(painter) self.views[view] = painter return view def del_view(self, view): assert view is not None with self.lock: if view in self.views: painter = self.views[view] self.grid.del_painter(painter) del self.views[view] def pause(self, delay = 0.0): ''' Defines a idle time to previewer ''' self.frame_timer.pause(delay) def stop(self): ''' Request the termination of panel ''' self.state = Preview.KILLED def is_running(self): ''' Verify if previewer state is RUNNING ''' return self.state == Preview.RUNNING def interactive(self, interact): ''' Defines if previewer works as a separate thread or not ''' if self.state == Preview.STARTING: self.threading = not interact def start(self): ''' Starts previewer ''' # Initialization pygame.init() self.state = Preview.RUNNING if self.threading: threading.Thread.__init__(self) threading.Thread.start(self) def run(self): ''' Run previewer continuously until it's received a termination sign ''' while self.step(): pass def step(self): ''' Executes a step (maybe with a frame drawing) and responds to the events Useful in mono threading applications (or main thread), which other class controls the execution and calls this method ''' # exit if don't run if self.state != Preview.RUNNING: return False # simple concurrence control with self.lock: # pre-process the buffered events self._process_events() # if don't need to force, verify timer (FPS limit) if self.frame_timer.idle(): return True # select a panel painter, panel, index, changed = self.grid.nextPainter() # if selector change, force to draw if changed: # if painter is None, just clear the panel if painter is None: panel.fill(self.bgcolor) else: painter.draw(panel, index) else: # if painter is None, there's nothing to do if painter is None: return True else: # painter was changed since last frame? if painter.changed(): painter.draw(panel, index) else: return True # go to next step panel.draw() pygame.display.flip() return True def _process_events(self): ''' Take actions in response to the events ''' for event in pygame.event.get(): if event.type == pygame.QUIT: self.state = Preview.STOPPING elif event.type == pygame.MOUSEBUTTONDOWN: self.mouse_drag = True if self.grid.select(event.pos): self.update_selector() elif event.type == pygame.MOUSEMOTION: if self.mouse_drag: if self.grid.select(event.pos): self.update_selector() elif event.type == pygame.MOUSEBUTTONUP: self.mouse_drag = False elif event.type == pygame.KEYDOWN: self.key_pressed = event.key self.keyboard_timer.set_delay(self.key_repeat_delay) self._process_key(self.key_pressed) elif event.type == pygame.KEYUP: self.key_pressed = None self.keyboard_timer.set_delay(0) # process key events if self.key_pressed is not None and not self.keyboard_timer.idle(): self.keyboard_timer.set_delay(self.key_repeat_rate) self._process_key(self.key_pressed) def _process_key(self, key): if key == pygame.K_DOWN: self.grid.line_down() elif key == pygame.K_UP: self.grid.line_up() elif key == pygame.K_PAGEDOWN: self.grid.page_down() elif key == pygame.K_PAGEUP: self.grid.page_up()