Exemplo n.º 1
0
class BravoFactory(Factory):
    """
    A ``Factory`` that creates ``BravoProtocol`` objects when connected to.
    """

    implements(IPushProducer)

    protocol = BravoProtocol

    timestamp = None
    time = 0
    day = 0
    eid = 1

    handshake_hook = None
    login_hook = None

    interface = ""

    def __init__(self, name):
        """
        Create a factory and world.

        ``name`` is the string used to look up factory-specific settings from
        the configuration.

        :param str name: internal name of this factory
        """

        self.name = name
        self.config_name = "world %s" % name

        self.port = configuration.getint(self.config_name, "port")
        self.interface = configuration.getdefault(self.config_name, "host", "")

    def startFactory(self):
        log.msg("Initializing factory for world '%s'..." % self.name)

        self.world = World(self.name)
        self.world.factory = self
        if configuration.has_option(self.config_name, "perm_cache"):
            cache_level = configuration.getint(self.config_name, "perm_cache")
            self.world.enable_cache(cache_level)

        self.protocols = dict()

        log.msg("Starting timekeeping...")
        self.timestamp = time()
        self.time = self.world.time
        self.update_season()
        self.time_loop = LoopingCall(self.update_time)
        self.time_loop.start(2)

        authenticator = configuration.get(self.config_name, "authenticator")
        selected = retrieve_named_plugins(IAuthenticator, [authenticator])[0]

        log.msg("Using authenticator %s" % selected.name)
        self.handshake_hook = selected.handshake
        self.login_hook = selected.login

        generators = configuration.getlist(self.config_name, "generators")
        generators = retrieve_sorted_plugins(ITerrainGenerator, generators)

        log.msg("Using generators %s" % ", ".join(i.name for i in generators))
        self.world.pipeline = generators

        automatons = configuration.getlist(self.config_name, "automatons")
        automatons = retrieve_named_plugins(IAutomaton, automatons)

        log.msg("Using automatons %s" % ", ".join(i.name for i in automatons))
        self.automatons = automatons

        self.chat_consumers = set()

        log.msg("Factory successfully initialized for world '%s'!" % self.name)

    def buildProtocol(self, addr):
        """
        Create a protocol.

        This overriden method provides early player entity registration, as a
        solution to the username/entity race that occurs on login.
        """

        banned = self.world.serializer.load_plugin_data("banned_ips")

        for ip in banned.split():
            if addr.host == ip:
                # Use BannedProtocol with extreme prejudice.
                log.msg("Kicking banned IP %s" % addr)
                p = BannedProtocol()
                p.factory = self
                return p

        log.msg("Starting connection for %s" % addr)
        p = self.protocol(self.name)
        p.factory = self

        self.register_entity(p)

        return p

    def create_entity(self, x, y, z, name, **kwargs):
        """
        Spawn an entirely new entity.

        Handles entity registration as well as instantiation.
        """

        location = Location()
        location.x = x
        location.y = y
        location.z = z
        entity = entities[name](eid=0, location=location, **kwargs)

        self.register_entity(entity)

        bigx = entity.location.x // 16
        bigz = entity.location.z // 16

        d = self.world.request_chunk(bigx, bigz)
        d.addCallback(lambda chunk: chunk.entities.add(entity))
        d.addCallback(lambda none: log.msg("Created entity %s" % entity))

        return entity

    def register_entity(self, entity):
        """
        Registers an entity with this factory.

        Registration is perhaps too fancy of a name; this method merely makes
        sure that the entity has a unique and usable entity ID.
        """

        if not entity.eid:
            self.eid += 1
            entity.eid = self.eid

        log.msg("Registered entity %s" % entity)

    def destroy_entity(self, entity):
        """
        Destroy an entity.

        The factory doesn't have to know about entities, but it is a good
        place to put this logic.
        """

        bigx = entity.location.x // 16
        bigz = entity.location.z // 16

        d = self.world.request_chunk(bigx, bigz)
        d.addCallback(lambda chunk: chunk.entities.discard(entity))
        d.addCallback(lambda none: log.msg("Destroyed entity %s" % entity))

    def update_time(self):
        """
        Update the in-game timer.

        The timer goes from 0 to 24000, both of which are high noon. The clock
        increments by 20 every second. Days are 20 minutes long.

        The day clock is incremented every in-game day, which is every 20
        minutes. The day clock goes from 0 to 360, which works out to a reset
        once every 5 days. This is a Babylonian in-game year.
        """

        t = time()
        self.time += 20 * (t - self.timestamp)
        self.timestamp = t

        while self.time > 24000:
            self.time -= 24000

            self.day += 1
            while self.day > 360:
                self.day -= 360

            self.update_season()

    def update_season(self):
        """
        Update the world's season.
        """

        plugins = configuration.getlistdefault(self.config_name, "seasons", [])
        for plugin in retrieve_named_plugins(ISeason, plugins):
            if plugin.day == self.day:
                self.world.season = plugin

    def chat(self, message):
        """
        Relay chat messages.

        Chat messages are sent to all connected clients, as well as to anybody
        consuming this factory.
        """

        for consumer in self.chat_consumers:
            consumer.write((self, message))

        # Prepare the message for chat packeting.
        for user in self.protocols:
            message = message.replace(user, chat_name(user))
        message = sanitize_chat(message)

        packet = make_packet("chat", message=message)
        self.broadcast(packet)

    def broadcast(self, packet):
        """
        Broadcast a packet to all connected players.
        """

        for player in self.protocols.itervalues():
            player.transport.write(packet)

    def broadcast_for_others(self, packet, protocol):
        """
        Broadcast a packet to all players except the originating player.

        Useful for certain packets like player entity spawns which should
        never be reflexive.
        """

        for player in self.protocols.itervalues():
            if player is not protocol:
                player.transport.write(packet)

    def broadcast_for_chunk(self, packet, x, z):
        """
        Broadcast a packet to all players that have a certain chunk loaded.

        `x` and `z` are chunk coordinates, not block coordinates.
        """

        for player in self.protocols.itervalues():
            if (x, z) in player.chunks:
                player.transport.write(packet)

    def flush_chunk(self, chunk):
        """
        Flush a damaged chunk to all players that have it loaded.
        """

        if chunk.is_damaged():
            packet = chunk.get_damage_packet()
            for player in self.protocols.itervalues():
                if (chunk.x, chunk.z) in player.chunks:
                    player.transport.write(packet)
            chunk.clear_damage()

    def give(self, coords, block, quantity):
        """
        Spawn a pickup at the specified coordinates.

        The coordinates need to be in pixels, not blocks.

        If the size of the stack is too big, multiple stacks will be dropped.

        :param tuple coords: coordinates, in pixels
        :param tuple block: key of block or item to drop
        :param int quantity: number of blocks to drop in the stack
        """

        x, y, z = coords

        while quantity > 0:
            entity = self.create_entity(x // 32,
                                        y // 32,
                                        z // 32,
                                        "Item",
                                        item=block,
                                        quantity=min(quantity, 64))

            packet = entity.save_to_packet()
            packet += make_packet("create", eid=entity.eid)
            self.broadcast(packet)

            quantity -= 64

    def players_near(self, player, radius):
        """
        Obtain other players within a radius of a given player.

        Radius is measured in blocks.
        """

        for i in (p for p in self.protocols.itervalues()
                  if player.location.distance(p.location) <= radius
                  and p.player != player):
            yield i.player

    def stopFactory(self):
        """
        Called before factory stops listening on ports. Used to perform
        shutdown tasks.
        """

        if not self.world.saving:
            return

        log.msg("Shutting down; flushing world data...")

        # Flush all dirty chunks to disk.
        for chunk in self.world.dirty_chunk_cache.itervalues():
            self.world.save_chunk(chunk)

        # Write back current world time.
        self.world.time = self.time
        self.world.serializer.save_level(self.world)

        log.msg("World data saved!")

    def pauseProducing(self):
        pass

    def resumeProducing(self):
        pass

    def stopProducing(self):
        pass
Exemplo n.º 2
0
class TestWorldChunks(unittest.TestCase):

    def setUp(self):
        self.name = "unittest"
        self.bcp = BravoConfigParser()

        self.bcp.add_section("world unittest")
        self.bcp.set("world unittest", "url", "")
        self.bcp.set("world unittest", "serializer", "memory")

        self.w = World(self.bcp, self.name)
        self.w.pipeline = []
        self.w.start()

    def tearDown(self):
        self.w.stop()

    def test_trivial(self):
        pass

    @inlineCallbacks
    def test_request_chunk_identity(self):
        first = yield self.w.request_chunk(0, 0)
        second = yield self.w.request_chunk(0, 0)
        self.assertIs(first, second)

    @inlineCallbacks
    def test_request_chunk_cached_identity(self):
        # Turn on the cache and get a few chunks in there, then request a
        # chunk that is in the cache.
        yield self.w.enable_cache(1)
        first = yield self.w.request_chunk(0, 0)
        second = yield self.w.request_chunk(0, 0)
        self.assertIs(first, second)

    @inlineCallbacks
    def test_get_block(self):
        chunk = yield self.w.request_chunk(0, 0)

        # Fill the chunk with random stuff.
        chunk.blocks = array("B")
        chunk.blocks.fromstring(os.urandom(32768))

        for x, y, z in product(xrange(2), repeat=3):
            # This works because the chunk is at (0, 0) so the coords don't
            # need to be adjusted.
            block = yield self.w.get_block((x, y, z))
            self.assertEqual(block, chunk.get_block((x, y, z)))

    @inlineCallbacks
    def test_get_metadata(self):
        chunk = yield self.w.request_chunk(0, 0)

        # Fill the chunk with random stuff.
        chunk.metadata = array("B")
        chunk.metadata.fromstring(os.urandom(32768))

        for x, y, z in product(xrange(2), repeat=3):
            # This works because the chunk is at (0, 0) so the coords don't
            # need to be adjusted.
            metadata = yield self.w.get_metadata((x, y, z))
            self.assertEqual(metadata, chunk.get_metadata((x, y, z)))

    @inlineCallbacks
    def test_get_block_readback(self):
        chunk = yield self.w.request_chunk(0, 0)

        # Fill the chunk with random stuff.
        chunk.blocks = array("B")
        chunk.blocks.fromstring(os.urandom(32768))

        # Evict the chunk and grab it again.
        yield self.w.save_chunk(chunk)
        del chunk
        chunk = yield self.w.request_chunk(0, 0)

        for x, y, z in product(xrange(2), repeat=3):
            # This works because the chunk is at (0, 0) so the coords don't
            # need to be adjusted.
            block = yield self.w.get_block((x, y, z))
            self.assertEqual(block, chunk.get_block((x, y, z)))

    @inlineCallbacks
    def test_get_block_readback_negative(self):
        chunk = yield self.w.request_chunk(-1, -1)

        # Fill the chunk with random stuff.
        chunk.blocks = array("B")
        chunk.blocks.fromstring(os.urandom(32768))

        # Evict the chunk and grab it again.
        yield self.w.save_chunk(chunk)
        del chunk
        chunk = yield self.w.request_chunk(-1, -1)

        for x, y, z in product(xrange(2), repeat=3):
            block = yield self.w.get_block((x - 16, y, z - 16))
            self.assertEqual(block, chunk.get_block((x, y, z)))

    @inlineCallbacks
    def test_get_metadata_readback(self):
        chunk = yield self.w.request_chunk(0, 0)

        # Fill the chunk with random stuff.
        chunk.metadata = array("B")
        chunk.metadata.fromstring(os.urandom(32768))

        # Evict the chunk and grab it again.
        yield self.w.save_chunk(chunk)
        del chunk
        chunk = yield self.w.request_chunk(0, 0)

        for x, y, z in product(xrange(2), repeat=3):
            # This works because the chunk is at (0, 0) so the coords don't
            # need to be adjusted.
            metadata = yield self.w.get_metadata((x, y, z))
            self.assertEqual(metadata, chunk.get_metadata((x, y, z)))

    @inlineCallbacks
    def test_world_level_mark_chunk_dirty(self):
        chunk = yield self.w.request_chunk(0, 0)

        # Reload chunk.
        yield self.w.save_chunk(chunk)
        del chunk
        chunk = yield self.w.request_chunk(0, 0)

        self.assertFalse(chunk.dirty)
        self.w.mark_dirty((12, 64, 4))
        chunk = yield self.w.request_chunk(0, 0)
        self.assertTrue(chunk.dirty)

    @inlineCallbacks
    def test_world_level_mark_chunk_dirty_offset(self):
        chunk = yield self.w.request_chunk(1, 2)

        # Reload chunk.
        yield self.w.save_chunk(chunk)
        del chunk
        chunk = yield self.w.request_chunk(1, 2)

        self.assertFalse(chunk.dirty)
        self.w.mark_dirty((29, 64, 43))
        chunk = yield self.w.request_chunk(1, 2)
        self.assertTrue(chunk.dirty)

    @inlineCallbacks
    def test_sync_get_block(self):
        chunk = yield self.w.request_chunk(0, 0)

        # Fill the chunk with random stuff.
        chunk.blocks = array("B")
        chunk.blocks.fromstring(os.urandom(32768))

        for x, y, z in product(xrange(2), repeat=3):
            # This works because the chunk is at (0, 0) so the coords don't
            # need to be adjusted.
            block = self.w.sync_get_block((x, y, z))
            self.assertEqual(block, chunk.get_block((x, y, z)))

    def test_sync_get_block_unloaded(self):
        self.assertRaises(ChunkNotLoaded, self.w.sync_get_block, (0, 0, 0))

    def test_sync_get_metadata_neighboring(self):
        """
        Even if a neighboring chunk is loaded, the target chunk could still be
        unloaded.

        Test with sync_get_metadata() to increase test coverage.
        """

        d = self.w.request_chunk(0, 0)

        @d.addCallback
        def cb(chunk):
            self.assertRaises(ChunkNotLoaded,
                              self.w.sync_get_metadata, (16, 0, 0))

        return d
Exemplo n.º 3
0
class BravoFactory(Factory):
    """
    A ``Factory`` that creates ``BravoProtocol`` objects when connected to.
    """

    implements(IPushProducer)

    protocol = BravoProtocol

    timestamp = None
    time = 0
    day = 0

    handshake_hook = None
    login_hook = None

    interface = ""

    def __init__(self, name):
        """
        Create a factory and world.

        ``name`` is the string used to look up factory-specific settings from
        the configuration.

        :param str name: internal name of this factory
        """

        log.msg("Initializing factory for world '%s'..." % name)

        self.name = name
        self.config_name = "world %s" % name

        self.port = configuration.getint(self.config_name, "port")
        self.interface = configuration.getdefault(self.config_name, "host",
            "")

        self.world = World(name)
        self.world.factory = self
        if configuration.has_option(self.config_name, "perm_cache"):
            cache_level = configuration.getint(self.config_name, "perm_cache")
            self.world.enable_cache(cache_level)

        self.protocols = dict()

        self.eid = 1

        self.time = self.world.time
        self.time_loop = LoopingCall(self.update_time)
        self.time_loop.start(2)

        authenticator = configuration.get(self.config_name, "authenticator")
        selected = retrieve_named_plugins(IAuthenticator, [authenticator])[0]

        log.msg("Using authenticator %s" % selected.name)
        self.handshake_hook = selected.handshake
        self.login_hook = selected.login

        generators = configuration.getlist(self.config_name, "generators")
        generators = retrieve_sorted_plugins(ITerrainGenerator, generators)

        log.msg("Using generators %s" % ", ".join(i.name for i in generators))
        self.world.pipeline = generators

        self.chat_consumers = set()

        log.msg("Factory successfully initialized for world '%s'!" % name)

    def buildProtocol(self, addr):
        """
        Create a protocol.

        This overriden method provides early player entity registration, as a
        solution to the username/entity race that occurs on login.
        """

        banned = self.world.serializer.load_plugin_data("banned_ips")

        for ip in banned.split():
            if addr.host == ip:
                # Use BannedProtocol with extreme prejudice.
                log.msg("Kicking banned IP %s" % addr)
                p = BannedProtocol()
                p.factory = self
                return p

        log.msg("Starting connection for %s" % addr)
        p = self.protocol(self.name)
        p.factory = self

        self.register_entity(p)

        return p

    def create_entity(self, x, y, z, name, **kwargs):
        """
        Spawn an entirely new entity.

        Handles entity registration as well as instantiation.
        """

        location = Location()
        location.x = x
        location.y = y
        location.z = z
        entity = entities[name](eid=0, location=location, **kwargs)

        self.register_entity(entity)

        bigx = entity.location.x // 16
        bigz = entity.location.z // 16

        d = self.world.request_chunk(bigx, bigz)
        d.addCallback(lambda chunk: chunk.entities.add(entity))
        d.addCallback(lambda none: log.msg("Created entity %s" % entity))

        return entity

    def register_entity(self, entity):
        """
        Registers an entity with this factory.

        Registration is perhaps too fancy of a name; this method merely makes
        sure that the entity has a unique and usable entity ID.
        """

        if not entity.eid:
            self.eid += 1
            entity.eid = self.eid

        log.msg("Registered entity %s" % entity)

    def destroy_entity(self, entity):
        """
        Destroy an entity.

        The factory doesn't have to know about entities, but it is a good
        place to put this logic.
        """

        bigx = entity.location.x // 16
        bigz = entity.location.z // 16

        d = self.world.request_chunk(bigx, bigz)
        d.addCallback(lambda chunk: chunk.entities.discard(entity))
        d.addCallback(lambda none: log.msg("Destroyed entity %s" % entity))

    def update_time(self):
        """
        Update the in-game timer.

        The timer goes from 0 to 24000, both of which are high noon. The clock
        increments by 20 every second. Days are 20 minutes long.

        The day clock is incremented every in-game day, which is every 20
        minutes. The day clock goes from 0 to 360, which works out to a reset
        once every 5 days. This is a Babylonian in-game year.
        """

        if self.timestamp is None:
            # First run since the start of the factory; re-init everything.
            self.timestamp = time()
            self.update_season()

        t = time()
        self.time += 20 * (t - self.timestamp)
        self.timestamp = t

        while self.time > 24000:
            self.time -= 24000

            self.day += 1
            while self.day > 360:
                self.day -= 360

            self.update_season()

    def update_season(self):
        """
        Update the world's season.
        """

        plugins = configuration.getlistdefault(self.config_name,
            "seasons", [])
        for plugin in retrieve_named_plugins(ISeason, plugins):
            if plugin.day == self.day:
                self.world.season = plugin

    def chat(self, message):
        """
        Relay chat messages.

        Chat messages are sent to all connected clients, as well as to anybody
        consuming this factory.
        """

        for consumer in self.chat_consumers:
            consumer.write((self, message))

        # Prepare the message for chat packeting.
        for user in self.protocols:
            message = message.replace(user, chat_name(user))
        message = sanitize_chat(message)

        packet = make_packet("chat", message=message)
        self.broadcast(packet)

    def broadcast(self, packet):
        """
        Broadcast a packet to all connected players.
        """

        for player in self.protocols.itervalues():
            player.transport.write(packet)

    def broadcast_for_others(self, packet, protocol):
        """
        Broadcast a packet to all players except the originating player.

        Useful for certain packets like player entity spawns which should
        never be reflexive.
        """

        for player in self.protocols.itervalues():
            if player is not protocol:
                player.transport.write(packet)

    def broadcast_for_chunk(self, packet, x, z):
        """
        Broadcast a packet to all players that have a certain chunk loaded.

        `x` and `z` are chunk coordinates, not block coordinates.
        """

        for player in self.protocols.itervalues():
            if (x, z) in player.chunks:
                player.transport.write(packet)

    def flush_chunk(self, chunk):
        """
        Flush a damaged chunk to all players that have it loaded.
        """

        if chunk.is_damaged():
            packet = chunk.get_damage_packet()
            for player in self.protocols.itervalues():
                if (chunk.x, chunk.z) in player.chunks:
                    player.transport.write(packet)
            chunk.clear_damage()

    def give(self, coords, block, quantity):
        """
        Spawn a pickup at the specified coordinates.

        The coordinates need to be in pixels, not blocks.

        If the size of the stack is too big, multiple stacks will be dropped.

        :param tuple coords: coordinates, in pixels
        :param tuple block: key of block or item to drop
        :param int quantity: number of blocks to drop in the stack
        """

        x, y, z = coords

        while quantity > 0:
            entity = self.create_entity(x // 32, y // 32, z // 32, "Item",
                item=block, quantity=min(quantity, 64))

            packet = entity.save_to_packet()
            packet += make_packet("create", eid=entity.eid)
            self.broadcast(packet)

            quantity -= 64

    def players_near(self, player, radius):
        """
        Obtain other players within a radius of a given player.

        Radius is measured in blocks.
        """

        for i in (p for p in self.protocols.itervalues()
            if player.location.distance(p.location) <= radius and
            p.player != player):
            yield i.player

    def stopFactory(self):
        """
        Called before factory stops listening on ports. Used to perform
        shutdown tasks.
        """

        if not self.world.saving:
            return

        log.msg("Shutting down; flushing world data...")

        # Flush all dirty chunks to disk.
        for chunk in self.world.dirty_chunk_cache.itervalues():
            self.world.save_chunk(chunk)

        # Write back current world time.
        self.world.time = self.time
        self.world.serializer.save_level(self.world)

        log.msg("World data saved!")

    def pauseProducing(self):
        pass

    def resumeProducing(self):
        pass

    def stopProducing(self):
        pass
Exemplo n.º 4
0
class TestWorldChunks(unittest.TestCase):
    def setUp(self):
        self.name = "unittest"
        self.bcp = BravoConfigParser()

        self.bcp.add_section("world unittest")
        self.bcp.set("world unittest", "url", "")
        self.bcp.set("world unittest", "serializer", "memory")

        self.w = World(self.bcp, self.name)
        self.w.pipeline = []
        self.w.start()

    def tearDown(self):
        self.w.stop()

    def test_trivial(self):
        pass

    @inlineCallbacks
    def test_request_chunk_identity(self):
        first = yield self.w.request_chunk(0, 0)
        second = yield self.w.request_chunk(0, 0)
        self.assertIs(first, second)

    @inlineCallbacks
    def test_request_chunk_cached_identity(self):
        # Turn on the cache and get a few chunks in there, then request a
        # chunk that is in the cache.
        yield self.w.enable_cache(1)
        first = yield self.w.request_chunk(0, 0)
        second = yield self.w.request_chunk(0, 0)
        self.assertIs(first, second)

    @inlineCallbacks
    def test_get_block(self):
        chunk = yield self.w.request_chunk(0, 0)

        # Fill the chunk with random stuff.
        chunk.blocks = array("B")
        chunk.blocks.fromstring(os.urandom(32768))

        for x, y, z in product(xrange(2), repeat=3):
            # This works because the chunk is at (0, 0) so the coords don't
            # need to be adjusted.
            block = yield self.w.get_block((x, y, z))
            self.assertEqual(block, chunk.get_block((x, y, z)))

    @inlineCallbacks
    def test_get_metadata(self):
        chunk = yield self.w.request_chunk(0, 0)

        # Fill the chunk with random stuff.
        chunk.metadata = array("B")
        chunk.metadata.fromstring(os.urandom(32768))

        for x, y, z in product(xrange(2), repeat=3):
            # This works because the chunk is at (0, 0) so the coords don't
            # need to be adjusted.
            metadata = yield self.w.get_metadata((x, y, z))
            self.assertEqual(metadata, chunk.get_metadata((x, y, z)))

    @inlineCallbacks
    def test_get_block_readback(self):
        chunk = yield self.w.request_chunk(0, 0)

        # Fill the chunk with random stuff.
        chunk.blocks = array("B")
        chunk.blocks.fromstring(os.urandom(32768))

        # Evict the chunk and grab it again.
        yield self.w.save_chunk(chunk)
        del chunk
        chunk = yield self.w.request_chunk(0, 0)

        for x, y, z in product(xrange(2), repeat=3):
            # This works because the chunk is at (0, 0) so the coords don't
            # need to be adjusted.
            block = yield self.w.get_block((x, y, z))
            self.assertEqual(block, chunk.get_block((x, y, z)))

    @inlineCallbacks
    def test_get_block_readback_negative(self):
        chunk = yield self.w.request_chunk(-1, -1)

        # Fill the chunk with random stuff.
        chunk.blocks = array("B")
        chunk.blocks.fromstring(os.urandom(32768))

        # Evict the chunk and grab it again.
        yield self.w.save_chunk(chunk)
        del chunk
        chunk = yield self.w.request_chunk(-1, -1)

        for x, y, z in product(xrange(2), repeat=3):
            block = yield self.w.get_block((x - 16, y, z - 16))
            self.assertEqual(block, chunk.get_block((x, y, z)))

    @inlineCallbacks
    def test_get_metadata_readback(self):
        chunk = yield self.w.request_chunk(0, 0)

        # Fill the chunk with random stuff.
        chunk.metadata = array("B")
        chunk.metadata.fromstring(os.urandom(32768))

        # Evict the chunk and grab it again.
        yield self.w.save_chunk(chunk)
        del chunk
        chunk = yield self.w.request_chunk(0, 0)

        for x, y, z in product(xrange(2), repeat=3):
            # This works because the chunk is at (0, 0) so the coords don't
            # need to be adjusted.
            metadata = yield self.w.get_metadata((x, y, z))
            self.assertEqual(metadata, chunk.get_metadata((x, y, z)))

    @inlineCallbacks
    def test_world_level_mark_chunk_dirty(self):
        chunk = yield self.w.request_chunk(0, 0)

        # Reload chunk.
        yield self.w.save_chunk(chunk)
        del chunk
        chunk = yield self.w.request_chunk(0, 0)

        self.assertFalse(chunk.dirty)
        self.w.mark_dirty((12, 64, 4))
        chunk = yield self.w.request_chunk(0, 0)
        self.assertTrue(chunk.dirty)

    @inlineCallbacks
    def test_world_level_mark_chunk_dirty_offset(self):
        chunk = yield self.w.request_chunk(1, 2)

        # Reload chunk.
        yield self.w.save_chunk(chunk)
        del chunk
        chunk = yield self.w.request_chunk(1, 2)

        self.assertFalse(chunk.dirty)
        self.w.mark_dirty((29, 64, 43))
        chunk = yield self.w.request_chunk(1, 2)
        self.assertTrue(chunk.dirty)

    @inlineCallbacks
    def test_sync_get_block(self):
        chunk = yield self.w.request_chunk(0, 0)

        # Fill the chunk with random stuff.
        chunk.blocks = array("B")
        chunk.blocks.fromstring(os.urandom(32768))

        for x, y, z in product(xrange(2), repeat=3):
            # This works because the chunk is at (0, 0) so the coords don't
            # need to be adjusted.
            block = self.w.sync_get_block((x, y, z))
            self.assertEqual(block, chunk.get_block((x, y, z)))

    def test_sync_get_block_unloaded(self):
        self.assertRaises(ChunkNotLoaded, self.w.sync_get_block, (0, 0, 0))

    def test_sync_get_metadata_neighboring(self):
        """
        Even if a neighboring chunk is loaded, the target chunk could still be
        unloaded.

        Test with sync_get_metadata() to increase test coverage.
        """

        d = self.w.request_chunk(0, 0)

        @d.addCallback
        def cb(chunk):
            self.assertRaises(ChunkNotLoaded, self.w.sync_get_metadata,
                              (16, 0, 0))

        return d
Exemplo n.º 5
0
config.set("world mapgen", "url", target)
config.set("world mapgen", "serializer", "beta")

world = World(config, "mapgen")
world.connect()
world.pipeline = pipeline
world.season = None
world.saving = True

counts = [1, 2, 4, 5, 8]
count = 0
total = size**2

cpu = 0
before = time.time()
for i, j in product(xrange(size), repeat=2):
    start = time.time()
    d = world.request_chunk(i, j)
    cpu += (time.time() - start)
    d.addCallback(lambda chunk: world.save_chunk(chunk))
    count += 1
    if count >= counts[0]:
        print "Status: %d/%d (%.2f%%)" % (count, total, count * 100 / total)
        counts.append(counts.pop(0) * 10)

taken = time.time() - before
print "Finished!"
print "Took %.2f seconds to generate (%dms/chunk)" % (taken,
                                                      taken * 1000 / size)
print "Spent %.2f seconds on CPU (%dms/chunk)" % (cpu, cpu * 1000 / size)
Exemplo n.º 6
0
    os.makedirs(target)

print "Making map of %dx%d chunks in %s" % (size, size, target)
print "Using pipeline: %s" % ", ".join(plugin.name for plugin in pipeline)

world = World(target)
world.pipeline = pipeline
world.season = None

counts = [1, 2, 4, 5, 8]
count = 0
total = size**2

cpu = 0
before = time.time()
for i, j in product(xrange(size), repeat=2):
    start = time.time()
    chunk = world.load_chunk(i, j)
    cpu += (time.time() - start)
    world.save_chunk(chunk)
    count += 1
    if count >= counts[0]:
        print "Status: %d/%d (%.2f%%)" % (count, total, count * 100 / total)
        counts.append(counts.pop(0) * 10)

taken = time.time() - before
print "Finished!"
print "Took %.2f seconds to generate (%dms/chunk)" % (taken,
                                                      taken * 1000 / size)
print "Spent %.2f seconds on CPU (%dms/chunk)" % (cpu, cpu * 1000 / size)
Exemplo n.º 7
0
    os.makedirs(target)

print "Making map of %dx%d chunks in %s" % (size, size, target)
print "Using pipeline: %s" % ", ".join(plugin.name for plugin in pipeline)

world = World(target)
world.pipeline = pipeline
world.season = None

counts = [1, 2, 4, 5, 8]
count = 0
total = size ** 2

cpu = 0
before = time.time()
for i, j in product(xrange(size), repeat=2):
    start = time.time()
    chunk = world.load_chunk(i, j)
    cpu += (time.time() - start)
    world.save_chunk(chunk)
    count += 1
    if count >= counts[0]:
        print "Status: %d/%d (%.2f%%)" % (count, total, count * 100 / total)
        counts.append(counts.pop(0) * 10)

taken = time.time() - before
print "Finished!"
print "Took %.2f seconds to generate (%dms/chunk)" % (taken,
    taken * 1000 / size)
print "Spent %.2f seconds on CPU (%dms/chunk)" % (cpu, cpu * 1000 / size)
Exemplo n.º 8
0
class TestWorldChunks(unittest.TestCase):

    def setUp(self):
        self.name = "unittest"
        self.d = tempfile.mkdtemp()

        bravo.config.configuration.add_section("world unittest")
        bravo.config.configuration.set("world unittest", "url", "file://%s" % self.d)
        bravo.config.configuration.set("world unittest", "serializer",
            "alpha")

        self.w = World(self.name)
        self.w.pipeline = []
        self.w.start()

    def tearDown(self):
        self.w.stop()
        del self.w

        shutil.rmtree(self.d)
        bravo.config.configuration.remove_section("world unittest")

    def test_trivial(self):
        pass

    @inlineCallbacks
    def test_get_block(self):
        chunk = yield self.w.request_chunk(0, 0)

        # Fill the chunk with random stuff.
        chunk.blocks = numpy.fromstring(numpy.random.bytes(chunk.blocks.size),
            dtype=numpy.uint8)
        chunk.blocks.shape = (16, 16, 128)

        for x, y, z in product(xrange(2), repeat=3):
            # This works because the chunk is at (0, 0) so the coords don't
            # need to be adjusted.
            block = yield self.w.get_block((x, y, z))
            self.assertEqual(block, chunk.get_block((x, y, z)))

    @inlineCallbacks
    def test_get_metadata(self):
        chunk = yield self.w.request_chunk(0, 0)

        # Fill the chunk with random stuff.
        chunk.metadata = numpy.fromstring(numpy.random.bytes(chunk.blocks.size),
            dtype=numpy.uint8)
        chunk.metadata.shape = (16, 16, 128)

        for x, y, z in product(xrange(2), xrange(2), xrange(2)):
            # This works because the chunk is at (0, 0) so the coords don't
            # need to be adjusted.
            metadata = yield self.w.get_metadata((x, y, z))
            self.assertEqual(metadata, chunk.get_metadata((x, y, z)))

    @inlineCallbacks
    def test_get_block_readback(self):
        chunk = yield self.w.request_chunk(0, 0)

        # Fill the chunk with random stuff.
        chunk.blocks = numpy.fromstring(numpy.random.bytes(chunk.blocks.size),
            dtype=numpy.uint8)
        chunk.blocks.shape = (16, 16, 128)

        # Evict the chunk and grab it again.
        self.w.save_chunk(chunk)
        del chunk
        self.w.chunk_cache.clear()
        self.w.dirty_chunk_cache.clear()
        chunk = yield self.w.request_chunk(0, 0)

        for x, y, z in product(xrange(2), xrange(2), xrange(2)):
            # This works because the chunk is at (0, 0) so the coords don't
            # need to be adjusted.
            block = yield self.w.get_block((x, y, z))
            self.assertEqual(block, chunk.get_block((x, y, z)))

    @inlineCallbacks
    def test_get_block_readback_negative(self):
        chunk = yield self.w.request_chunk(-1, -1)

        # Fill the chunk with random stuff.
        chunk.blocks = numpy.fromstring(numpy.random.bytes(chunk.blocks.size),
            dtype=numpy.uint8)
        chunk.blocks.shape = (16, 16, 128)

        # Evict the chunk and grab it again.
        self.w.save_chunk(chunk)
        del chunk
        self.w.chunk_cache.clear()
        self.w.dirty_chunk_cache.clear()
        chunk = yield self.w.request_chunk(-1, -1)

        for x, y, z in product(xrange(2), xrange(2), xrange(2)):
            block = yield self.w.get_block((x - 16, y, z - 16))
            self.assertEqual(block, chunk.get_block((x, y, z)))

    @inlineCallbacks
    def test_get_metadata_readback(self):
        chunk = yield self.w.request_chunk(0, 0)

        # Fill the chunk with random stuff.
        chunk.metadata = numpy.fromstring(numpy.random.bytes(chunk.blocks.size),
            dtype=numpy.uint8)
        chunk.metadata.shape = (16, 16, 128)

        # Evict the chunk and grab it again.
        self.w.save_chunk(chunk)
        del chunk
        self.w.chunk_cache.clear()
        self.w.dirty_chunk_cache.clear()
        chunk = yield self.w.request_chunk(0, 0)

        for x, y, z in product(xrange(2), xrange(2), xrange(2)):
            # This works because the chunk is at (0, 0) so the coords don't
            # need to be adjusted.
            metadata = yield self.w.get_metadata((x, y, z))
            self.assertEqual(metadata, chunk.get_metadata((x, y, z)))

    @inlineCallbacks
    def test_world_level_mark_chunk_dirty(self):
        chunk = yield self.w.request_chunk(0, 0)

        # Reload chunk.
        self.w.save_chunk(chunk)
        del chunk
        self.w.chunk_cache.clear()
        self.w.dirty_chunk_cache.clear()
        chunk = yield self.w.request_chunk(0, 0)

        self.assertFalse(chunk.dirty)
        self.w.mark_dirty((12, 64, 4))
        chunk = yield self.w.request_chunk(0, 0)
        self.assertTrue(chunk.dirty)

    @inlineCallbacks
    def test_world_level_mark_chunk_dirty_offset(self):
        chunk = yield self.w.request_chunk(1, 2)

        # Reload chunk.
        self.w.save_chunk(chunk)
        del chunk
        self.w.chunk_cache.clear()
        self.w.dirty_chunk_cache.clear()
        chunk = yield self.w.request_chunk(1, 2)

        self.assertFalse(chunk.dirty)
        self.w.mark_dirty((29, 64, 43))
        chunk = yield self.w.request_chunk(1, 2)
        self.assertTrue(chunk.dirty)

    @inlineCallbacks
    def test_sync_get_block(self):
        chunk = yield self.w.request_chunk(0, 0)

        # Fill the chunk with random stuff.
        chunk.blocks = numpy.fromstring(numpy.random.bytes(chunk.blocks.size),
            dtype=numpy.uint8)
        chunk.blocks.shape = (16, 16, 128)

        for x, y, z in product(xrange(2), repeat=3):
            # This works because the chunk is at (0, 0) so the coords don't
            # need to be adjusted.
            block = self.w.sync_get_block((x, y, z))
            self.assertEqual(block, chunk.get_block((x, y, z)))

    def test_sync_get_block_unloaded(self):
        self.assertRaises(ChunkNotLoaded, self.w.sync_get_block, (0, 0, 0))

    def test_sync_get_metadata_neighboring(self):
        """
        Even if a neighboring chunk is loaded, the target chunk could still be
        unloaded.

        Test with sync_get_metadata() to increase test coverage.
        """

        d = self.w.request_chunk(0, 0)

        @d.addCallback
        def cb(chunk):
            self.assertRaises(ChunkNotLoaded,
                              self.w.sync_get_metadata, (16, 0, 0))

        return d
Exemplo n.º 9
0
class TestWorldChunks(unittest.TestCase):
    def setUp(self):
        self.name = "unittest"
        self.d = tempfile.mkdtemp()
        self.bcp = BravoConfigParser()

        self.bcp.add_section("world unittest")
        self.bcp.set("world unittest", "url", "file://%s" % self.d)
        self.bcp.set("world unittest", "serializer", "alpha")

        self.w = World(self.bcp, self.name)
        self.w.pipeline = []
        self.w.start()

    def tearDown(self):
        self.w.stop()
        shutil.rmtree(self.d)

    def test_trivial(self):
        pass

    @inlineCallbacks
    def test_get_block(self):
        chunk = yield self.w.request_chunk(0, 0)

        # Fill the chunk with random stuff.
        chunk.blocks = numpy.fromstring(numpy.random.bytes(chunk.blocks.size),
                                        dtype=numpy.uint8)
        chunk.blocks.shape = (16, 16, 128)

        for x, y, z in product(xrange(2), repeat=3):
            # This works because the chunk is at (0, 0) so the coords don't
            # need to be adjusted.
            block = yield self.w.get_block((x, y, z))
            self.assertEqual(block, chunk.get_block((x, y, z)))

    @inlineCallbacks
    def test_get_metadata(self):
        chunk = yield self.w.request_chunk(0, 0)

        # Fill the chunk with random stuff.
        chunk.metadata = numpy.fromstring(numpy.random.bytes(
            chunk.blocks.size),
                                          dtype=numpy.uint8)
        chunk.metadata.shape = (16, 16, 128)

        for x, y, z in product(xrange(2), repeat=3):
            # This works because the chunk is at (0, 0) so the coords don't
            # need to be adjusted.
            metadata = yield self.w.get_metadata((x, y, z))
            self.assertEqual(metadata, chunk.get_metadata((x, y, z)))

    @inlineCallbacks
    def test_get_block_readback(self):
        chunk = yield self.w.request_chunk(0, 0)

        # Fill the chunk with random stuff.
        chunk.blocks = numpy.fromstring(numpy.random.bytes(chunk.blocks.size),
                                        dtype=numpy.uint8)
        chunk.blocks.shape = (16, 16, 128)

        # Evict the chunk and grab it again.
        self.w.save_chunk(chunk)
        del chunk
        self.w.chunk_cache.clear()
        self.w.dirty_chunk_cache.clear()
        chunk = yield self.w.request_chunk(0, 0)

        for x, y, z in product(xrange(2), repeat=3):
            # This works because the chunk is at (0, 0) so the coords don't
            # need to be adjusted.
            block = yield self.w.get_block((x, y, z))
            self.assertEqual(block, chunk.get_block((x, y, z)))

    @inlineCallbacks
    def test_get_block_readback_negative(self):
        chunk = yield self.w.request_chunk(-1, -1)

        # Fill the chunk with random stuff.
        chunk.blocks = numpy.fromstring(numpy.random.bytes(chunk.blocks.size),
                                        dtype=numpy.uint8)
        chunk.blocks.shape = (16, 16, 128)

        # Evict the chunk and grab it again.
        self.w.save_chunk(chunk)
        del chunk
        self.w.chunk_cache.clear()
        self.w.dirty_chunk_cache.clear()
        chunk = yield self.w.request_chunk(-1, -1)

        for x, y, z in product(xrange(2), repeat=3):
            block = yield self.w.get_block((x - 16, y, z - 16))
            self.assertEqual(block, chunk.get_block((x, y, z)))

    @inlineCallbacks
    def test_get_metadata_readback(self):
        chunk = yield self.w.request_chunk(0, 0)

        # Fill the chunk with random stuff.
        chunk.metadata = numpy.fromstring(numpy.random.bytes(
            chunk.blocks.size),
                                          dtype=numpy.uint8)
        chunk.metadata.shape = (16, 16, 128)

        # Evict the chunk and grab it again.
        self.w.save_chunk(chunk)
        del chunk
        self.w.chunk_cache.clear()
        self.w.dirty_chunk_cache.clear()
        chunk = yield self.w.request_chunk(0, 0)

        for x, y, z in product(xrange(2), repeat=3):
            # This works because the chunk is at (0, 0) so the coords don't
            # need to be adjusted.
            metadata = yield self.w.get_metadata((x, y, z))
            self.assertEqual(metadata, chunk.get_metadata((x, y, z)))

    @inlineCallbacks
    def test_world_level_mark_chunk_dirty(self):
        chunk = yield self.w.request_chunk(0, 0)

        # Reload chunk.
        self.w.save_chunk(chunk)
        del chunk
        self.w.chunk_cache.clear()
        self.w.dirty_chunk_cache.clear()
        chunk = yield self.w.request_chunk(0, 0)

        self.assertFalse(chunk.dirty)
        self.w.mark_dirty((12, 64, 4))
        chunk = yield self.w.request_chunk(0, 0)
        self.assertTrue(chunk.dirty)

    @inlineCallbacks
    def test_world_level_mark_chunk_dirty_offset(self):
        chunk = yield self.w.request_chunk(1, 2)

        # Reload chunk.
        self.w.save_chunk(chunk)
        del chunk
        self.w.chunk_cache.clear()
        self.w.dirty_chunk_cache.clear()
        chunk = yield self.w.request_chunk(1, 2)

        self.assertFalse(chunk.dirty)
        self.w.mark_dirty((29, 64, 43))
        chunk = yield self.w.request_chunk(1, 2)
        self.assertTrue(chunk.dirty)

    @inlineCallbacks
    def test_sync_get_block(self):
        chunk = yield self.w.request_chunk(0, 0)

        # Fill the chunk with random stuff.
        chunk.blocks = numpy.fromstring(numpy.random.bytes(chunk.blocks.size),
                                        dtype=numpy.uint8)
        chunk.blocks.shape = (16, 16, 128)

        for x, y, z in product(xrange(2), repeat=3):
            # This works because the chunk is at (0, 0) so the coords don't
            # need to be adjusted.
            block = self.w.sync_get_block((x, y, z))
            self.assertEqual(block, chunk.get_block((x, y, z)))

    def test_sync_get_block_unloaded(self):
        self.assertRaises(ChunkNotLoaded, self.w.sync_get_block, (0, 0, 0))

    def test_sync_get_metadata_neighboring(self):
        """
        Even if a neighboring chunk is loaded, the target chunk could still be
        unloaded.

        Test with sync_get_metadata() to increase test coverage.
        """

        d = self.w.request_chunk(0, 0)

        @d.addCallback
        def cb(chunk):
            self.assertRaises(ChunkNotLoaded, self.w.sync_get_metadata,
                              (16, 0, 0))

        return d