From f26a74d224f401b6c1557d0b8031ebb5ddc9d811 Mon Sep 17 00:00:00 2001 From: Richard Feistenauer Date: Sat, 5 Jan 2019 17:38:21 +0100 Subject: [PATCH] redraw speedup though use of generator --- scripts/main_ui.py | 9 ++- src/cellular_automaton/ca_display.py | 63 +++++++++++--------- src/cellular_automaton/ca_grid.py | 14 +++-- src/cellular_automaton/cellular_automaton.py | 51 +++++++--------- 4 files changed, 72 insertions(+), 65 deletions(-) diff --git a/scripts/main_ui.py b/scripts/main_ui.py index fb374c7..125c616 100644 --- a/scripts/main_ui.py +++ b/scripts/main_ui.py @@ -2,11 +2,12 @@ import random -from cellular_automaton.cellular_automaton import CellularAutomaton +from cellular_automaton.cellular_automaton import CellularAutomaton, CellularAutomatonEvolver from cellular_automaton.ca_rule import Rule from cellular_automaton.ca_neighborhood import MooreNeighborhood, EdgeRule from cellular_automaton.ca_display import PyGameFor2D from cellular_automaton.ca_cell_state import CellState +from cellular_automaton.ca_grid import Grid class TestRule(Rule): @@ -43,6 +44,8 @@ class MyStatus(CellState): if __name__ == "__main__": random.seed(1000) rule = TestRule() - ca = CellularAutomaton([400, 400], MooreNeighborhood(EdgeRule.FIRST_AND_LAST_CELL_OF_DIMENSION_ARE_NEIGHBORS), rule) - ca_window = PyGameFor2D([1000, 800], ca, 5) + ca = CellularAutomaton(Grid([400, 400], MooreNeighborhood(EdgeRule.FIRST_AND_LAST_CELL_OF_DIMENSION_ARE_NEIGHBORS)), + rule) + ca_evolver = CellularAutomatonEvolver(2) + ca_window = PyGameFor2D([1000, 800], ca, ca_evolver, 5) ca_window.main_loop() diff --git a/src/cellular_automaton/ca_display.py b/src/cellular_automaton/ca_display.py index 23c5f70..5a520bc 100644 --- a/src/cellular_automaton/ca_display.py +++ b/src/cellular_automaton/ca_display.py @@ -2,49 +2,45 @@ import pygame import time import operator -from cellular_automaton.cellular_automaton import CellularAutomaton +from cellular_automaton.cellular_automaton import CellularAutomaton, CellularAutomatonEvolver + + +class _DisplayInfo: + def __init__(self, grid_size, grid_pos, cell_size, screen): + self.grid_size = grid_size + self.grid_pos = grid_pos + self.cell_size = cell_size + self.screen = screen class DisplayFor2D: def __init__(self, grid_rect: list, cellular_automaton: CellularAutomaton, screen): - self.grid_size = grid_rect[-2:] - self.grid_pos = grid_rect[:2] self._cellular_automaton = cellular_automaton - self.screen = screen - - self.cell_size = self._calculate_cell_display_size() - - self._surfaces_to_update = [] + cell_size = self._calculate_cell_display_size(grid_rect[-2:]) + self._display_info = _DisplayInfo(grid_rect[-2:], grid_rect[:2], cell_size, screen) def set_cellular_automaton(self, cellular_automaton): self._cellular_automaton = cellular_automaton def _redraw_cellular_automaton(self): - for cell in self._cellular_automaton.grid.get_cells().values(): - self._redraw_cell(cell) - pygame.display.update(self._surfaces_to_update) - self._surfaces_to_update = [] + pygame.display.update(list(_cell_redraw_rectangles(self._cellular_automaton.grid.get_cells().values(), + self._cellular_automaton.evolution_iteration_index, + self._display_info))) - def _redraw_cell(self, cell): - if cell.is_set_for_redraw: - cell_color = cell.state.get_state_draw_color(self._cellular_automaton.get_iteration_index()) - surface_pos = self._calculate_cell_position(cell) - surface_pos = list(map(operator.add, surface_pos, self.grid_pos)) - self._surfaces_to_update.append(self.screen.fill(cell_color, (surface_pos, self.cell_size))) - cell.is_set_for_redraw = False - - def _calculate_cell_position(self, cell): - return list(map(operator.mul, self.cell_size, cell.coordinate)) - - def _calculate_cell_display_size(self): + def _calculate_cell_display_size(self, grid_size): grid_dimension = self._cellular_automaton.grid.get_dimension() - return list(map(operator.truediv, self.grid_size, grid_dimension)) + return list(map(operator.truediv, grid_size, grid_dimension)) class PyGameFor2D: - def __init__(self, windows_size: list, cellular_automaton: CellularAutomaton, ca_iterations_per_draw): + def __init__(self, + windows_size: list, + cellular_automaton: CellularAutomaton, + cellular_automaton_evolver: CellularAutomatonEvolver, + ca_iterations_per_draw): self._window_size = windows_size self._cellular_automaton = cellular_automaton + self._cellular_automaton_evolver = cellular_automaton_evolver self._ca_steps_per_draw = ca_iterations_per_draw pygame.init() @@ -69,7 +65,7 @@ class PyGameFor2D: while running: time_ca_start = time.time() - self._cellular_automaton.evolve_x_times(self._ca_steps_per_draw) + self._cellular_automaton_evolver.evolve_x_times(self._cellular_automaton, self._ca_steps_per_draw) time_ca_end = time.time() self.ca_display._redraw_cellular_automaton() time_ds_end = time.time() @@ -79,3 +75,16 @@ class PyGameFor2D: if event.type == pygame.QUIT: running = False + +def _cell_redraw_rectangles(cells, evolution_index, display_info): + for cell in cells: + if cell.is_set_for_redraw: + cell_color = cell.state.get_state_draw_color(evolution_index) + cell_pos = _calculate_cell_position(display_info.cell_size, cell) + surface_pos = list(map(operator.add, cell_pos, display_info.grid_pos)) + yield display_info.screen.fill(cell_color, (surface_pos, display_info.cell_size)) + cell.is_set_for_redraw = False + + +def _calculate_cell_position(cell_size, cell): + return list(map(operator.mul, cell_size, cell.coordinate)) diff --git a/src/cellular_automaton/ca_grid.py b/src/cellular_automaton/ca_grid.py index d87bf39..437c50e 100644 --- a/src/cellular_automaton/ca_grid.py +++ b/src/cellular_automaton/ca_grid.py @@ -6,11 +6,13 @@ class Grid: def __init__(self, dimension: list, neighborhood: Neighborhood): self._dimension = dimension self._cells = {} - self._neighborhood = neighborhood + self._active_cells = {} + self._init_cells(neighborhood) + + def _init_cells(self, neighborhood): self._create_cells() - self._set_cell_neighbours() - + self._set_cell_neighbours(neighborhood) self._active_cells = self._cells.copy() self._set_all_cells_active() @@ -68,10 +70,10 @@ class Grid: new_cod = coordinate + [cell_index] recursion_method(dimension_index + 1, new_cod) - def _set_cell_neighbours(self): + def _set_cell_neighbours(self, neighborhood): for cell in self._cells.values(): - neighbours_coordinates = self._neighborhood.calculate_cell_neighbor_coordinates(cell.coordinate, - self._dimension) + neighbours_coordinates = neighborhood.calculate_cell_neighbor_coordinates(cell.coordinate, + self._dimension) cell.neighbours = list(map(self._get_cell_by_coordinate, neighbours_coordinates)) def _get_cell_by_coordinate(self, coordinate): diff --git a/src/cellular_automaton/cellular_automaton.py b/src/cellular_automaton/cellular_automaton.py index 9aaa59c..e740f10 100644 --- a/src/cellular_automaton/cellular_automaton.py +++ b/src/cellular_automaton/cellular_automaton.py @@ -1,61 +1,54 @@ from cellular_automaton.ca_grid import Grid from cellular_automaton.ca_rule import Rule -from cellular_automaton.ca_neighborhood import Neighborhood class CellularAutomaton: - def __init__(self, dimension: list, neighborhood: Neighborhood, evolution_rule: Rule=None): - self.grid = Grid(dimension, neighborhood) + def __init__(self, grid: Grid, evolution_rule: Rule): + self.grid = grid self.evolution_rule = evolution_rule - self.iteration = 0 - self.test_number = 0 + self.evolution_iteration_index = 0 - def set_rule(self, rule: Rule): - """ Set new evolution rule. - :param rule: - :return: - """ - self.evolution_rule = rule - def get_iteration_index(self): - """ Get the count of evolution cycles done. - :return: Evolution steps done. - """ - return self.iteration +class CellularAutomatonEvolver: + def __init__(self, process_count: int = 1): + self.__processes = process_count + self.__cellular_automaton = None - def evolve_x_times(self, evolutions: int): + def evolve_x_times(self, cellular_automaton: CellularAutomaton, evolution_steps: int): """ Evolve all cells for x time steps. - :param evolutions: the count of evolutions done. + :param evolution_steps: the count of evolutions done. :return: True if all cells are inactive """ - for evo in range(evolutions): - finished = self.evolve() + for evo in range(evolution_steps): + finished = self.evolve(cellular_automaton) if finished: return True return False - def evolve(self): + def evolve(self, cellular_automaton: CellularAutomaton): """ Evolves all active cells for one time step. :return: True if all cells are inactive. """ + self.__cellular_automaton = cellular_automaton if self._is_evolution_finished(): return True else: - self.iteration += 1 + cellular_automaton.evolution_iteration_index += 1 self._evolve_all_active_cells() return False + def _is_evolution_finished(self): + return len(self.__cellular_automaton.grid.get_active_cell_names()) == 0 + def _evolve_all_active_cells(self): - active_cells = self.grid.get_active_cells() - self.grid.clear_active_cells() + active_cells = self.__cellular_automaton.grid.get_active_cells() + self.__cellular_automaton.grid.clear_active_cells() self._evolve_cells(active_cells.values()) - def _is_evolution_finished(self): - return len(self.grid.get_active_cell_names()) == 0 - def _evolve_cells(self, cells: list): + cellular_automaton = self.__cellular_automaton for cell in cells: - active = self.evolution_rule.evolve_cell(cell, self.iteration) + active = cellular_automaton.evolution_rule.evolve_cell(cell, cellular_automaton.evolution_iteration_index) if active: - self.grid.set_cells_active([cell] + cell.neighbours) + cellular_automaton.grid.set_cells_active([cell] + cell.neighbours)