def test_garbage_collection_precondition(self): pc = PhysicsCache() pc.acquire(1) pc.release(1) pc.acquire(2) pc.release(2) # verify that game_loop_finish does not permit collection by itself, # but does in conjunction with graphics_loop_finish pc.game_loop_finish(0) pc._garbage_collect() self.assertEqual(pc.count, 3) self.assertEqual(pc.latest, 2) self.assertTrue(pc.has(0)) pc.graphics_loop_finish(0) pc._garbage_collect() self.assertEqual(pc.count, 2) self.assertEqual(pc.latest, 2) self.assertTrue(not pc.has(0)) # verify that graphics_loop_finish does not permit collection by # itself, but does in conjunction with game_loop_finish pc.graphics_loop_finish(1) pc._garbage_collect() self.assertEqual(pc.count, 2) self.assertEqual(pc.latest, 2) self.assertTrue(pc.has(1)) pc.game_loop_finish(1) pc._garbage_collect() self.assertEqual(pc.count, 1) self.assertEqual(pc.latest, 2) self.assertTrue(not pc.has(1))
def test_latest(self): pc = PhysicsCache() self.assertEqual(pc.latest, 0) pc.acquire(3) pc.release(3) pc.acquire(2) pc.release(2) self.assertEqual(pc.latest, 3)
def test_has(self): pc = PhysicsCache() self.assertTrue(pc.has(0)) self.assertTrue(not pc.has(3)) pc.acquire(3) self.assertTrue(not pc.has(3)) pc.release(3) self.assertTrue(pc.has(3))
def test_garbage_collection_protects_latest(self): pc = PhysicsCache() pc.acquire(1) pc.release(1) # verify that the latest snapshot will not permit itself to be deleted pc.graphics_loop_finish(1) pc.game_loop_finish(1) pc._garbage_collect(2) self.assertTrue(pc.has(1))
def test_count(self): pc = PhysicsCache() count = len([i for i in pc._snapshots if pc._snapshots[i].ready]) self.assertEqual(count, 1) self.assertEqual(pc.count, count) pc.acquire(3) pc.release(3) pc.acquire(2) count = len([i for i in pc._snapshots if pc._snapshots[i].ready]) self.assertEqual(count, 2) self.assertEqual(pc.count, count) pc.release(2) count = len([i for i in pc._snapshots if pc._snapshots[i].ready]) self.assertEqual(count, 3) self.assertEqual(pc.count, count)
def test_garbage_collection_cascade(self): pc = PhysicsCache() pc.acquire(1) pc.release(1) pc.acquire(2) pc.release(2) # verify that a snapshot that's okay_to_delete also causes earlier # snapshots to be okay_to_delete pc.graphics_loop_finish(1) pc.game_loop_finish(1) pc._garbage_collect(2) self.assertEqual(pc.count, 1) self.assertEqual(pc.latest, 2) self.assertTrue(not pc.has(0)) self.assertTrue(not pc.has(1)) self.assertTrue(pc.has(2))
class Universe: def __init__(self,generator=None,paused=True,G = 8.648208e-15): self.bodies = [] self.time = 0 # gravitational constant is in kilometers cubed per kilogram per turn squared # 1 turn = 6 minutes self.G = G self.view = None self._turn_left = 1 self._paused = paused self.pause_lock = Lock() self.generator = generator self.sun = None self.physics_cache = PhysicsCache(self) self.next_turn = clock() + 360 self._seconds_per_turn = 360 self.running = False def get_seconds_per_turn(self): return self._seconds_per_turn def set_seconds_per_turn(self, seconds_per_turn): turn_left = (self.next_turn - clock()) / self._seconds_per_turn self._seconds_per_turn = seconds_per_turn self.next_turn = clock() + (turn_left * self._seconds_per_turn) seconds_per_turn = property(get_seconds_per_turn, set_seconds_per_turn) def get_turn_left(self): if self.paused: return self._turn_left return max(0, self.next_turn - clock()) / self.seconds_per_turn turn_left = property(get_turn_left) def get_paused(self): return self._paused def set_paused(self, paused): self.pause_lock.acquire() # if unpausing if self.paused and not paused: self.next_turn = clock() + (self._turn_left * self.seconds_per_turn) # self._turn_left only has meaning while paused del self._turn_left # else if pausing elif not self.paused and paused: self._turn_left = self.turn_left self._paused = paused self.pause_lock.release() paused = property(get_paused, set_paused) def get_center_of_mass(self): total_mass = sum(body.mass for body in self.bodies) if total_mass == 0: return Vector(0,0,0) return sum((body.get_position(self.time) * body.mass) for body in self.bodies)/total_mass center_of_mass = property(get_center_of_mass) def calculate_physics(self, turn): self.physics_cache.acquire(turn) if not self.physics_cache.has(turn): for body in self.bodies: gravity_sum = 0 satellite_gravity_sum = 0 for other in self.bodies: if body.primary == other or body.primary == other.primary: component = body.attraction(other, turn-1) gravity_sum += component satellite_gravity_sum += component elif body == other.primary: gravity_sum += body.attraction(other, turn-1) # otherwise, do not calculate an interaction position = body.get_position(turn-1) + body.get_velocity(turn-1) velocity = body.get_velocity(turn-1) + (gravity_sum * self.G) sat_accel = satellite_gravity_sum * self.G self.physics_cache.record(turn, body, position, velocity, sat_accel) self.physics_cache.release(turn) def pass_turn(self): self.time += 1 if self.generator: dev = self.generator.generate_development() if dev: print("Development: %d at t=%d" % (dev,self.time)) # give the goahead from our side to delete this record self.physics_cache.game_loop_finish(self.time - 1) def travel_time(self,b1,b2,accel): velocity_diff = (b1.velocity - b2.velocity).magnitude distance = (b1.position - b2.position).magnitude return ceil((velocity_diff/accel)+(distance/sqrt(accel*distance/4))) def ui_loop(self): self.view = Interface(self) self.view.ui_loop() def physics_cache_loop(self): start_time = clock() while self.view: self.calculate_physics(self.physics_cache.latest + 1) if self.time_per_snapshot < self.seconds_per_turn or self.paused: self.physics_cache.garbage_collect(2) self.time_per_snapshot = (clock() - start_time + self.time_per_snapshot*9) / 10 start_time = clock() def describe_system(self): plural = "s" if self.time == 1: plural = "" print("time = "+str(self.time)+" turn"+plural) sun = self.bodies[0] print(sun.name+": mass="+('%.2E' % sun.mass)) for i in range(1,len(self.bodies)): bodyi = self.bodies[i] dist = (self.bodies[0].position - bodyi.position).magnitude orbit_speed = (self.bodies[0].velocity - bodyi.velocity).magnitude print(bodyi.name+": dist=("+('%.2E' % dist)+"), orbit speed=("+('%.2E' % orbit_speed)+"), mass="+('%.2E' % bodyi.mass)) print() def make_system_tree(self, turn=None): sorted_bodies = sorted(self.bodies, key=lambda b: b.mass) for i in range(0,len(sorted_bodies)-1): bodyi = sorted_bodies[i] highest_influence = 0 primary = None for j in range(i+1,len(sorted_bodies)): bodyj = sorted_bodies[j] dist = bodyi.distance(bodyj, turn) influence = bodyj.mass / dist / dist if influence > highest_influence: highest_influence = influence primary = bodyj bodyi.primary = primary def run(self, visible = True): self.start_time = clock() if visible: ui_t = Thread(target=self.ui_loop) ui_t.start() while not self.view: pass phys_t = Thread(target=self.physics_cache.loop) phys_t.start() self.next_turn = clock() + self.seconds_per_turn self.running = True while self.running: while (self.paused or clock() < self.next_turn) and self.view: pass if (self.physics_cache.latest <= self.time): self.paused = True else: self.pass_turn() self.next_turn += self.seconds_per_turn def stop(self): self.running = False self.physics_cache.running = False self.view = None