Chinaunix首页 | 论坛 | 博客
  • 博客访问: 5096543
  • 博文数量: 921
  • 博客积分: 16037
  • 博客等级: 上将
  • 技术积分: 8469
  • 用 户 组: 普通用户
  • 注册时间: 2006-04-05 02:08
文章分类

全部博文(921)

文章存档

2020年(1)

2019年(3)

2018年(3)

2017年(6)

2016年(47)

2015年(72)

2014年(25)

2013年(72)

2012年(125)

2011年(182)

2010年(42)

2009年(14)

2008年(85)

2007年(89)

2006年(155)

分类: Python/Ruby

2012-04-10 19:11:14


  1. import random
  2. import sys
  3. import weakref

  4. from ampoule import deferToAMPProcess

  5. from twisted.internet.defer import Deferred, succeed
  6. from twisted.internet.task import LoopingCall
  7. from twisted.python.filepath import FilePath

  8. from nbt.nbt import NBTFile

  9. from bravo.chunk import Chunk
  10. from bravo.config import configuration
  11. from bravo.remote import MakeChunk
  12. from bravo.serialize import LevelSerializer

  13. def base36(i):
  14.     """
  15.     Return the string representation of i in base 36, using lowercase letters.
  16.     """

  17.     letters = "0123456789abcdefghijklmnopqrstuvwxyz"

  18.     if i < 0:
  19.         i = -i
  20.         signed = True
  21.     elif i == 0:
  22.         return "0"
  23.     else:
  24.         signed = False

  25.     s = ""

  26.     while i:
  27.         i, digit = divmod(i, 36)
  28.         s = letters[digit] + s

  29.     if signed:
  30.         s = "-" + s

  31.     return s

  32. def names_for_chunk(x, z):
  33.     """
  34.     Calculate the folder and file names for given chunk coordinates.
  35.     """

  36.     first = base36(x & 63)
  37.     second = base36(z & 63)
  38.     third = "c.%s.%s.dat" % (base36(x), base36(z))

  39.     return first, second, third

  40. class World(LevelSerializer):
  41.     """
  42.     Object representing a world on disk.

  43.     Worlds are composed of levels and chunks, each of which corresponds to
  44.     exactly one file on disk. Worlds also contain saved player data.
  45.     """

  46.     season = None
  47.     """
  48.     The current `ISeason`.
  49.     """

  50.     saving = True
  51.     """
  52.     Whether objects belonging to this world may be written out to disk.
  53.     """

  54.     def __init__(self, folder):
  55.         """
  56.         Load a world from disk.

  57.         :Parameters:
  58.             folder : str
  59.                 The directory containing the world.
  60.         """

  61.         self.folder = FilePath(folder)
  62.         if not self.folder.exists():
  63.             self.folder.makedirs()

  64.         self.chunk_cache = weakref.WeakValueDictionary()
  65.         self.dirty_chunk_cache = dict()

  66.         self._pending_chunks = dict()

  67.         self.spawn = (0, 0, 0)
  68.         self.seed = random.randint(0, sys.maxint)

  69.         level = self.folder.child("level.dat")
  70.         if level.exists() and level.getsize():
  71.             self.load_from_tag(NBTFile(fileobj=level.open("r")))

  72.         tag = self.save_to_tag()
  73.         tag.write_file(fileobj=level.open("w"))

  74.         self.chunk_management_loop = LoopingCall(self.sort_chunks)
  75.         self.chunk_management_loop.start(1)

  76.     def sort_chunks(self):
  77.         """
  78.         Sort out the internal caches.

  79.         This method will always block when there are dirty chunks.
  80.         """

  81.         first = True

  82.         all_chunks = dict(self.dirty_chunk_cache)
  83.         all_chunks.update(self.chunk_cache)
  84.         self.chunk_cache.clear()
  85.         self.dirty_chunk_cache.clear()
  86.         for coords, chunk in all_chunks.iteritems():
  87.             if chunk.dirty:
  88.                 if first:
  89.                     first = False
  90.                     self.save_chunk(chunk)
  91.                     self.chunk_cache[coords] = chunk
  92.                 else:
  93.                     self.dirty_chunk_cache[coords] = chunk
  94.             else:
  95.                 self.chunk_cache[coords] = chunk

  96.     def save_off(self):
  97.         """
  98.         Disable saving to disk.

  99.         This is useful for accessing the world on disk without Bravo
  100.         interfering, for backing up the world.
  101.         """

  102.         if not self.saving:
  103.             return

  104.         d = dict(self.chunk_cache)
  105.         self.chunk_cache = d
  106.         self.saving = False

  107.     def save_on(self):
  108.         """
  109.         Enable saving to disk.
  110.         """

  111.         if self.saving:
  112.             return

  113.         d = weakref.WeakValueDictionary(self.chunk_cache)
  114.         self.chunk_cache = d
  115.         self.saving = True

  116.     def populate_chunk(self, chunk):
  117.         """
  118.         Recreate data for a chunk.

  119.         This method does arbitrary terrain generation depending on the current
  120.         plugins, and then regenerates the chunk metadata so that the chunk can
  121.         be sent to clients.

  122.         A lot of maths may be done by this method, so do not call it unless
  123.         absolutely necessary, e.g. when the chunk is created for the first
  124.         time.
  125.         """

  126.         for stage in self.pipeline:
  127.             stage.populate(chunk, self.seed)

  128.         chunk.regenerate()

  129.     def request_chunk(self, x, z):
  130.         """
  131.         Request a ``Chunk`` to be delivered later.
  132.         """

  133.         if (x, z) in self.chunk_cache:
  134.             return succeed(self.chunk_cache[x, z])
  135.         elif (x, z) in self.dirty_chunk_cache:
  136.             return succeed(self.dirty_chunk_cache[x, z])
  137.         elif (x, z) in self._pending_chunks:
  138.             # Rig up another Deferred and wrap it up in a to-go box.
  139.             d = Deferred()
  140.             self._pending_chunks[x, z].chainDeferred(d)
  141.             return d

  142.         chunk = Chunk(x, z)

  143.         first, second, filename = names_for_chunk(x, z)
  144.         f = self.folder.child(first).child(second)
  145.         if not f.exists():
  146.             f.makedirs()
  147.         f = f.child(filename)
  148.         if f.exists() and f.getsize():
  149.             tag = NBTFile(fileobj=f.open("r"))
  150.             chunk.load_from_tag(tag)

  151.         if chunk.populated:
  152.             self.chunk_cache[x, z] = chunk
  153.             return succeed(chunk)

  154.         d = deferToAMPProcess(MakeChunk, x=x, z=z, seed=self.seed,
  155.             generators=configuration.get("bravo", "generators"))
  156.         self._pending_chunks[x, z] = d

  157.         def pp(kwargs):
  158.             chunk.blocks.ravel()[:] = [ord(i) for i in kwargs["blocks"]]
  159.             chunk.heightmap.ravel()[:] = [ord(i) for i in kwargs["heightmap"]]
  160.             chunk.metadata.ravel()[:] = [ord(i) for i in kwargs["metadata"]]
  161.             chunk.skylight.ravel()[:] = [ord(i) for i in kwargs["skylight"]]
  162.             chunk.lightmap.ravel()[:] = [ord(i) for i in kwargs["blocklight"]]

  163.             chunk.populated = True
  164.             chunk.dirty = True

  165.             # Apply the current season to the chunk.
  166.             if self.season:
  167.                 self.season.transform(chunk)

  168.             # Since this chunk hasn't been given to any player yet, there's no
  169.             # conceivable way that any meaningful damage has been accumulated;
  170.             # anybody loading any part of this chunk will want the entire thing.
  171.             # Thus, it should start out undamaged.
  172.             chunk.clear_damage()

  173.             self.dirty_chunk_cache[x, z] = chunk
  174.             del self._pending_chunks[x, z]

  175.             return chunk

  176.         # Set up callbacks.
  177.         d.addCallback(pp)
  178.         # Multiple people might be subscribed to this pending callback. We're
  179.         # going to keep it for ourselves and fork off another Deferred for our
  180.         # caller.
  181.         forked = Deferred()
  182.         d.chainDeferred(forked)
  183.         forked.addCallback(lambda none: chunk)
  184.         return forked

  185.     def load_chunk(self, x, z):
  186.         """
  187.         Retrieve a ``Chunk``.

  188.         This method does lots of automatic caching of chunks to ensure that
  189.         disk I/O is kept to a minimum.
  190.         """

  191.         if (x, z) in self.chunk_cache:
  192.             return self.chunk_cache[x, z]
  193.         elif (x, z) in self.dirty_chunk_cache:
  194.             return self.dirty_chunk_cache[x, z]

  195.         chunk = Chunk(x, z)

  196.         first, second, filename = names_for_chunk(x, z)
  197.         f = self.folder.child(first).child(second)
  198.         if not f.exists():
  199.             f.makedirs()
  200.         f = f.child(filename)
  201.         if f.exists() and f.getsize():
  202.             tag = NBTFile(fileobj=f.open("r"))
  203.             chunk.load_from_tag(tag)

  204.         if chunk.populated:
  205.             self.chunk_cache[x, z] = chunk
  206.         else:
  207.             self.populate_chunk(chunk)
  208.             chunk.populated = True
  209.             chunk.dirty = True

  210.             self.dirty_chunk_cache[x, z] = chunk

  211.         # Apply the current season to the chunk.
  212.         if self.season:
  213.             self.season.transform(chunk)

  214.         # Since this chunk hasn't been given to any player yet, there

from:
阅读(905) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~