class ControllerProtocol(LineReceiver): """ Protocol for dealing with controller requests. """ def connectionMade(self): self.logger = ColouredLogger(debug) peer = self.transport.getPeer() self.logger.debug("Control connection made from %s:%s" % (peer.host, peer.port)) self.factory, self.controller_factory = self.factory.main_factory, self.factory def connectionLost(self, reason): peer = self.transport.getPeer() self.logger.debug("Control connection lost from %s:%s" % (peer.host, peer.port)) def sendJson(self, data): self.sendLine(simplejson.dumps(data)) def lineReceived(self, line): data = simplejson.loads(line) peer = self.transport.getPeer() if data['password'] != self.factory.controller_password: self.sendJson({"error": "invalid password"}) self.logger.info("Control: Invalid password %s (%s:%s)" % (data, peer.host, peer.port)) else: command = data['command'].lower() try: func = getattr(self, "command%s" % command.title()) except AttributeError: self.sendJson({"error": "unknown command %s" % command}) else: self.logger.debug("Control: %s %s (%s:%s)" % (command.upper(), data, peer.host, peer.port)) try: func(data) except Exception, e: self.sendJson({"error": "%s" % e}) traceback.print_exc()
class BlockStore(Thread): """ A class which deals with storing the block worlds, flushing them, etc. """ def __init__(self, blocks_path, sx, sy, sz): Thread.__init__(self) self.x, self.y, self.z = sx, sy, sz self.blocks_path = blocks_path self.world_name = os.path.basename(os.path.dirname(blocks_path)) self.in_queue = Queue() self.out_queue = Queue() self.logger = ColouredLogger() def run(self): # Initialise variables self.physics = False self.physics_engine = Physics(self) self.raw_blocks = None # Read this from any thread but please update through TASK_BLOCKSET so queued_blocks is updated self.running = True self.unflooding = False self.finite_water = False self.queued_blocks = {} # Blocks which need to be flushed into the file. self.create_raw_blocks() # Start physics engine self.physics_engine.start() # Main eval loop while self.running: try: # Pop something off the queue task = self.in_queue.get() # If we've been asked to flush, do so, and say we did. if task[0] is TASK_FLUSH: self.flush() self.out_queue.put([TASK_FLUSH]) # New block? elif task[0] is TASK_BLOCKSET: try: self[task[1]] = task[2] if len(task) == 4 and task[3] == True: # Tells the server to update the given block for clients. self.out_queue.put([TASK_BLOCKSET, (task[1][0], task[1][1], task[1][2], task[2])]) except AssertionError: self.logger.warning("Tried to set a block at %s in %s!" % (task[1], self.world_name)) # Asking for a block? elif task[0] is TASK_BLOCKGET: self.out_queue.put([TASK_BLOCKGET, task[1], self[task[1]]]) # Perhaps physics was enabled? elif task[0] is TASK_PHYSICSOFF: self.logger.debug("Disabling physics on '%s'..." % self.world_name) self.disable_physics() # Or disabled? elif task[0] is TASK_PHYSICSON: self.logger.debug("Enabling physics on '%s'..." % self.world_name) self.enable_physics() # I can haz finite water tiem? elif task[0] is TASK_FWATERON: self.logger.debug("Enabling finite water on '%s'..." % self.world_name) self.finite_water = True # Noes, no more finite water. elif task[0] is TASK_FWATEROFF: self.logger.debug("Disabling finite water on '%s'..." % self.world_name) self.finite_water = False # Do they need to do a Moses? elif task[0] is TASK_UNFLOOD: self.logger.debug("Unflood started on '%s'..." % self.world_name) self.unflooding = True # Perhaps that's it, and we need to stop? elif task[0] is TASK_STOP: self.logger.debug("Stopping block store '%s'..." % self.world_name) self.physics_engine.stop() self.flush() self.logger.debug("Stopped block store '%s'." % self.world_name) return # ??? else: raise ValueError("Unknown BlockStore task: %s" % task) except (KeyboardInterrupt, IOError): pass def enable_physics(self): "Turns on physics" self.physics = True def disable_physics(self): "Disables physics, and clears the in-memory store." self.physics = False def create_raw_blocks(self): "Reads in the gzipped data into a raw array" # Open the blocks file fh = gzip.GzipFile(self.blocks_path) self.raw_blocks = array('c') # Read off the size header fh.read(4) # Copy into the array in chunks chunk = fh.read(2048) while chunk: self.raw_blocks.extend(chunk) chunk = fh.read(2048) fh.close() def get_offset(self, x, y, z): "Turns block coordinates into a data offset" assert 0 <= x < self.x assert 0 <= y < self.y assert 0 <= z < self.z return y * (self.x * self.z) + z * (self.x) + x def get_coords(self, offset): "Turns a data offset into coordinates" x = offset % self.x z = (offset // self.x) % self.z y = offset // (self.x * self.z) return x, y, z def message(self, message): "Sends a message out to users about this World." self.out_queue.put([TASK_MESSAGE, message]) def __setitem__(self, (x, y, z), block): "Set a block in this level to the given value." assert isinstance(block, str) and len(block) == 1 # Save to queued blocks offset = self.get_offset(x, y, z) self.queued_blocks[offset] = block # And directly to raw blocks, if we must if self.raw_blocks: self.raw_blocks[offset] = block # Ask the physics engine if they'd like a look at that self.physics_engine.handle_change(offset, block)
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])
# Arc is copyright 2009-2011 the Arc team and other contributors. # Arc is licensed under the BSD 2-Clause modified License. # To view more details, please see the "LICENSING" file in the "docs" folder of the Arc Package. import sys from arc.logger import ColouredLogger debug = (True if "--debug" in sys.argv else False) logger = ColouredLogger() logger.debug("Imported arc/ folder.") del logger del ColouredLogger del sys
makedatfile("config/data/titles.dat") debug = (True if "--debug" in sys.argv else False) logger = ColouredLogger(debug) try: from colorama import init except ImportError: logger.warn("Colorama is not installed - console colours DISABLED.") except Exception as e: logger.warn("Unable to import colorama: %s" % e) logger.warn("Console colours DISABLED.") else: init() logger.stdout("&f") logger.debug("&fIf you see this, debug mode is &eon&f!") logger.info("&fColorama &ainstalled&f - Console colours &cENABLED&f.") def doExit(): if os.name == "nt": raw_input("\nYou may now close the server console window.") else: raw_input("\nPlease press enter to exit.") def main(): global logger logger.info("Starting up &bArc&f v%s" % VERSION) factory = ArcFactory(debug) try: