def run(self): while self.running: if self.blockstore.physics: if self.blockstore.unflooding: # Do n fluid removals updates = 0 for offset, block in enumerate(self.blockstore.raw_blocks): if block == CHR_LAVA or block == CHR_WATER or block == CHR_SPOUT or block == CHR_LAVA_SPOUT: x, y, z = self.blockstore.get_coords(offset) self.set_block((x, y, z), BLOCK_AIR) updates += 1 if updates >= LIMIT_UNFLOOD: break else: # Unflooding complete. self.blockstore.unflooding = False self.blockstore.message(COLOUR_YELLOW + "Unflooding complete.") self.changed.clear() self.working = set() else: # If this is the first of a physics run, redo the queues from scratch if not self.was_physics or self.was_unflooding: self.logger.debug("Queue everything for '%s'." % self.blockstore.world_name) self.changed.clear() self.working = Popxrange(0, (self.blockstore.x * self.blockstore.y * self.blockstore.z)) # if working list is empty then copy changed set to working set # otherwise keep using the working set till empty elif len(self.working) == 0: self.logger.debug("Performing expand checks for '%s' with %d changes." % ( self.blockstore.world_name, len(self.changed))) changedfixed = self.changed # 'changedfixed' is 'changed' so gets all updates self.changed = set() # until 'changed' is a new set. This and the above statment are ATOMIC self.working = set() # changes from a Popxrange to a set while len(changedfixed) > 0: self.expand_checks(changedfixed.pop()) self.logger.debug("Starting physics run for '%s' with %d checks." % ( self.blockstore.world_name, len(self.working))) updates = 0 try: for x in xrange(LIMIT_CHECKS): offset = self.working.pop() updates += self.handle(offset) except KeyError: pass #if overflow and (time.time() - self.last_lag > self.LAG_INTERVAL): #self.blockstore.message("Physics is currently lagging in %(id)s.") #self.last_lag = time.time() self.logger.debug("Ended physics run for '%s' with %d updates and %d checks remaining." % ( self.blockstore.world_name, updates, len(self.working))) else: if self.was_physics: self.blockstore.unflooding = False self.changed.clear() self.working = set() self.was_physics = self.blockstore.physics self.was_unflooding = self.blockstore.unflooding # Wait till next iter time.sleep(0.7) # TODO change this so takes into account run time
class Physics(Thread): """ Given a BlockStore, works out what needs doing (water, grass etc.) and send the changes back to the BlockStore. """ # This thread: # * Receives changes by ATOMIC updates to 'changed' eg. self.changed.add(offset) # * Sends changes by queueing updates in the blockstore # * Reads but doesn't modify self.blockstore.raw_blocks[] # Optimised by only checking blocks near changes, advantages are a small work set and very up-to-date def __init__(self, blockstore): Thread.__init__(self) self.blockstore = blockstore #self.setDaemon(True) # means that python can terminate even if still active #self.work = Event() # TODO use event to start and stop self.last_lag = 0 self.running = True self.was_physics = False self.was_unflooding = False self.changed = set() self.working = set() # could be a list or a sorted list but why bother (world updates may appear in random order but most of the time so many get updated it should be unnoticable) self.sponge_locations = set() self.logger = ColouredLogger(debug) def stop(self): self.running = False self.join() def run(self): while self.running: if self.blockstore.physics: if self.blockstore.unflooding: # Do n fluid removals updates = 0 for offset, block in enumerate(self.blockstore.raw_blocks): if block == CHR_LAVA or block == CHR_WATER or block == CHR_SPOUT or block == CHR_LAVA_SPOUT: x, y, z = self.blockstore.get_coords(offset) self.set_block((x, y, z), BLOCK_AIR) updates += 1 if updates >= LIMIT_UNFLOOD: break else: # Unflooding complete. self.blockstore.unflooding = False self.blockstore.message(COLOUR_YELLOW + "Unflooding complete.") self.changed.clear() self.working = set() else: # If this is the first of a physics run, redo the queues from scratch if not self.was_physics or self.was_unflooding: self.logger.debug("Queue everything for '%s'." % self.blockstore.world_name) self.changed.clear() self.working = Popxrange(0, (self.blockstore.x * self.blockstore.y * self.blockstore.z)) # if working list is empty then copy changed set to working set # otherwise keep using the working set till empty elif len(self.working) == 0: self.logger.debug("Performing expand checks for '%s' with %d changes." % ( self.blockstore.world_name, len(self.changed))) changedfixed = self.changed # 'changedfixed' is 'changed' so gets all updates self.changed = set() # until 'changed' is a new set. This and the above statment are ATOMIC self.working = set() # changes from a Popxrange to a set while len(changedfixed) > 0: self.expand_checks(changedfixed.pop()) self.logger.debug("Starting physics run for '%s' with %d checks." % ( self.blockstore.world_name, len(self.working))) updates = 0 try: for x in xrange(LIMIT_CHECKS): offset = self.working.pop() updates += self.handle(offset) except KeyError: pass #if overflow and (time.time() - self.last_lag > self.LAG_INTERVAL): #self.blockstore.message("Physics is currently lagging in %(id)s.") #self.last_lag = time.time() self.logger.debug("Ended physics run for '%s' with %d updates and %d checks remaining." % ( self.blockstore.world_name, updates, len(self.working))) else: if self.was_physics: self.blockstore.unflooding = False self.changed.clear() self.working = set() self.was_physics = self.blockstore.physics self.was_unflooding = self.blockstore.unflooding # Wait till next iter time.sleep(0.7) # TODO change this so takes into account run time def handle_change(self, offset, block): # must be ATOMIC "Gets called when a block is changed, with its offset and type." self.changed.add(offset) def set_block(self, (x, y, z), block): # only place blockstore is updated "Call to queue a block change, with its position and type." self.blockstore.in_queue.put([TASK_BLOCKSET, (x, y, z), chr(block), True])