diff --git a/scripts/main_ui.py b/scripts/main_ui.py index d43ffbf..50e1db2 100644 --- a/scripts/main_ui.py +++ b/scripts/main_ui.py @@ -32,7 +32,7 @@ class WorldGeneratorWindow: surfaces_to_update = [] for cell in self._cellular_automaton.grid.get_active_cells().values(): - if not cell.dirty: + if not cell.is_dirty(): continue cell_coordinate = cell.coordinate status = cell.get_status_for_iteration(self._cellular_automaton.get_iteration_index()) @@ -45,6 +45,7 @@ class WorldGeneratorWindow: surface_pos = [x * y for x, y in zip(cell_size, cell_coordinate)] surface_pos[1] += 20 surfaces_to_update.append(self.screen.fill(cell_color, (surface_pos, cell_size))) + cell.release_dirty() pygame.display.update(surfaces_to_update) def main_loop(self): @@ -75,6 +76,7 @@ class WorldGeneratorWindow: class TestRule(Rule): def evolve_cell(self, cell, neighbors, iteration_index): + active = False last_iteration = iteration_index - 1 if cell.get_status_for_iteration(last_iteration) is None: rand = random.randrange(0, 101, 1) @@ -83,15 +85,18 @@ class TestRule(Rule): cell.set_status_for_iteration([rand], iteration_index) cell.set_status_for_iteration([rand], iteration_index + 1) if rand != 0: - cell.dirty = True + active = True + cell.set_dirty() elif len(neighbors) == 8: left_neighbour_status = neighbors[3].get_status_for_iteration(last_iteration) - cell.set_status_for_iteration(left_neighbour_status, iteration_index) - return cell.dirty + active = cell.set_status_for_iteration(left_neighbour_status, iteration_index) + if active: + cell.set_dirty() + return active if __name__ == "__main__": rule = TestRule() - ca = CellularAutomaton([500, 500], MooreNeighborhood(EdgeRule.IGNORE_EDGE_CELLS), rule, thread_count=1) + ca = CellularAutomaton([10, 10], MooreNeighborhood(EdgeRule.IGNORE_EDGE_CELLS), rule, thread_count=2) ca_window = WorldGeneratorWindow([1000, 730], ca) ca_window.main_loop() diff --git a/src/cellular_automaton/ca_cell.py b/src/cellular_automaton/ca_cell.py index 63c2620..c725c9d 100644 --- a/src/cellular_automaton/ca_cell.py +++ b/src/cellular_automaton/ca_cell.py @@ -4,19 +4,29 @@ class Cell: self.coordinate = coordinate self.neighbours = [] self._status = [None, None] - self.dirty = False + self._dirty = False def set_neighbours(self, neighbours: list): self.neighbours = neighbours + def is_dirty(self): + return self._dirty + + def set_dirty(self): + self._dirty = True + + def release_dirty(self): + self._dirty = False + def set_status_for_iteration(self, new_status, iteration): """ Will set the new status for Iteration. :param new_status: The new status to set. :param iteration: Uses the iteration index, to differ between current and next state. + :return True if status has changed. """ self._status[iteration % 2] = new_status - self.dirty = self._status[0] != self._status[1] + return self._status[0] != self._status[1] def get_status_for_iteration(self, iteration): """ Will return the status for the iteration. diff --git a/src/cellular_automaton/cellular_automaton.py b/src/cellular_automaton/cellular_automaton.py index 8367152..b8fafe8 100644 --- a/src/cellular_automaton/cellular_automaton.py +++ b/src/cellular_automaton/cellular_automaton.py @@ -1,4 +1,4 @@ -import threading +import multiprocessing import time from cellular_automaton.ca_grid import Grid @@ -9,84 +9,73 @@ from cellular_automaton.ca_neighborhood import Neighborhood class CellularAutomaton: def __init__(self, dimension: list, neighborhood: Neighborhood, rule_: Rule=None, thread_count: int=4): self.grid = Grid(dimension, neighborhood) - self._rule = rule_ + self.rule = rule_ self._thread_count = thread_count - self._iteration = 0 + self.iteration = 0 + self.test_number = 0 def set_rule(self, rule: Rule): - self._rule = rule + self.rule = rule def set_thread_count(self, thread_count: int): self._thread_count = thread_count def get_iteration_index(self): - return self._iteration + return self.iteration def evolve(self): + """ Evolves all active cells for one time step. + :return: True if all cells are inactive. + """ if self._all_cells_are_inactive(): return True else: - self._iteration += 1 - self._delegate_evolve_to_threads() + self.iteration += 1 + self._start_multi_process_cell_evolution() return False - def _delegate_evolve_to_threads(self): - cell_lists_for_threats = self.create_cell_lists_for_threads() + def _start_multi_process_cell_evolution(self): + """ Evolves the cells in separate threads """ + lists_of_cell_names = self._create_list_of_cell_names_lists_for_all_threads() self.grid.clear_active_cells() - threads = self._start_treads_to_evolve_grid(cell_lists_for_threats) - self._wait_for_all_threads_to_finish(threads) + jobs = self._start_treads_to_evolve_grid(lists_of_cell_names) + self._wait_for_all_threads_to_finish(jobs) + print(self.test_number) def _all_cells_are_inactive(self): return len(self.grid.get_names_of_active_cells()) == 0 - def create_cell_lists_for_threads(self): + def _create_list_of_cell_names_lists_for_all_threads(self): active_cells = self.grid.get_names_of_active_cells() cell_count_per_thread = int(len(active_cells) / self._thread_count) - return self.divide_active_cells(cell_count_per_thread, active_cells) + return self._divide_list_in_parts_of_size(active_cells, cell_count_per_thread) @staticmethod - def divide_active_cells(cell_count_per_thread, active_cells): + def _divide_list_in_parts_of_size(active_cells: list, cell_count_per_thread: int): return [active_cells[i:i + cell_count_per_thread] for i in range(0, len(active_cells), cell_count_per_thread)] - def _start_treads_to_evolve_grid(self, cell_lists_for_threats): - threads = [] + def _start_treads_to_evolve_grid(self, lists_of_cell_names): + jobs = [] for t in range(self._thread_count): - new_thread = _EvolutionThread(self.grid, self._rule, cell_lists_for_threats[t], self._iteration) - threads.append(new_thread) - new_thread.start() - return threads + process = multiprocessing.Process(target=_evolve_cells, args=(self, lists_of_cell_names[t])) + jobs.append(process) + process.start() + return jobs @staticmethod - def _wait_for_all_threads_to_finish(threads): - for thread in threads: - while not thread.is_finished(): - time.sleep(0.01) - - thread.join() + def _wait_for_all_threads_to_finish(jobs): + for process in jobs: + process.join() -class _EvolutionThread(threading.Thread): - def __init__(self, grid: Grid, rule: Rule, cell_list: list, iteration: int): - super(_EvolutionThread, self).__init__() - self._grid = grid - self._rule = rule - self._cell_list = cell_list - self._next_state = [] - self._finished = False - self._iteration = iteration +def _evolve_cells(cellular_automaton, cell_names: list): + time.sleep(2) + cellular_automaton.test_number += 1 + print(cellular_automaton.grid) + for cell_name in cell_names: + cell_info = cellular_automaton.grid.get_cell_and_neighbors(cell_name) + active = cellular_automaton.rule.evolve_cell(cell_info[0], cell_info[1], cellular_automaton.iteration) - def run(self): - for cell in self._cell_list: - cell_info = self._grid.get_cell_and_neighbors(cell) - active = self._rule.evolve_cell(cell_info[0], cell_info[1], self._iteration) - - if active: - self._grid.set_cell_and_neighbours_active(cell_info) - self._finished = True - - def get_new_cell_states(self): - return self._next_state - - def is_finished(self): - return self._finished + if active: + cellular_automaton.grid.set_cell_and_neighbours_active(cell_info)