-
Notifications
You must be signed in to change notification settings - Fork 1
/
simulation.py
139 lines (109 loc) · 4.41 KB
/
simulation.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
class SimulationError(Exception):
def __init__(self, value):
self.value = value
def __str__(self):
return repr(self.value)
from multiprocessing import Process, Value
import logging
import random
from library import Library
from phased_loop import PhasedLoop
from swig_modules import automata
import visualization
logger = logging.getLogger(__name__)
""" Controls a simulation. """
class Simulation:
""" x_size: The horizontal size of this simulation's grid.
y_size: The vertical size of this simulation's grid.
iteration_time: How much time each iteration encompasses. """
def __init__(self, x_size, y_size, iteration_time):
self.__x_size = x_size
self.__y_size = y_size
self.__iteration_time = iteration_time
# A list of organisms to get loaded as soon as we fork.
self.__to_load = []
# Generate random sets of non-repeating numbers that we will use for placing
# grid objects.
self.__random_x = list(range(0, x_size))
self.__random_y = list(range(0, y_size))
random.shuffle(self.__random_x)
random.shuffle(self.__random_y)
# The separate process that will be used to run the simulation.
self.simulation_process = Process(target = self.__run_simulation_process)
# The current iteration of the simulation.
self.__iteration = Value("i", 0)
""" Do necessary initialization, then run forever. """
def __run_simulation_process(self):
# The grid for this simulation.
self.__grid = automata.Grid(self.__x_size, self.__y_size)
# The visualization of the grid for this simulation.
self.__grid_vis = visualization.GridVisualization(
self.__x_size, self.__y_size)
# The list of objects on the grid.
self.__grid_objects = []
# The frequency for updating the graphics.
graphics_limiter = PhasedLoop(30)
# The frequency for updating the simulation.
# TODO(danielp): Make this rate user-settable.
simulation_limiter = PhasedLoop(1)
# Load all the organisms that we needed to load.
for organism in self.__to_load:
library_name = organism[0]
name = organism[1]
x_pos = organism[2]
y_pos = organism[3]
library = Library(library_name)
organism = library.load_organism(name, self.__grid, (x_pos, y_pos))
logger.info("Adding new grid object at (%d, %d)." % (x_pos, y_pos))
self.__grid_objects.append(organism)
# Add a visualization for the organism.
visualization.GridObjectVisualization(self.__grid_vis, organism)
# Update the grid to bake everything in its initial position.
if not self.__grid.Update():
logger.log_and_raise(SimulationError, "Initial grid update failed.")
# Now that the visualization is populated, draw a key for it.
self.__key = visualization.Key(self.__grid_vis)
# Now run the simulation.
while True:
PhasedLoop.limit_fastest()
if simulation_limiter.should_run():
# Run the simulation.
self.__run_iteration()
if graphics_limiter.should_run():
self.__grid_vis.update()
self.__key.update()
""" Completely update the grid a single time. """
def __run_iteration(self):
# Update the status of all objects.
to_delete = []
for grid_object in self.__grid_objects:
if not grid_object.update(self.__iteration_time):
# Organism died. Remove it. (Already logged.)
to_delete.append(grid_object)
for organism in to_delete:
self.__grid_objects.remove(organism)
# Update the grid.
if not self.__grid.Update():
logger.log_and_raise(SimulationError, "Grid Update() failed unexpectedly.")
self.__iteration.value += 1
logger.debug("Running iteration %d." % (self.__iteration.value))
""" Start the simulation. """
def start(self):
# The simulation gets run in a separate process.
self.simulation_process.start()
""" Get the current iteration number.
Returns: The current iteration number. """
def get_iterations(self):
return self.__iteration.value
""" Adds a new organism to the simulation.
library: The object to add.
name: The name of the species. """
def add_organism(self, library, name):
# Pick a random position for the organism.
if not len(self.__random_x):
# We're out of space.
logger.log_and_raise(SimulationError,
"Cannot place object, no space on grid.")
x_pos = self.__random_x.pop()
y_pos = self.__random_y.pop()
self.__to_load.append((library, name, x_pos, y_pos))