twisted学习笔记

上周末突击了一小下,2dx client跑到python server前面去了:

client网络连接、数据包收发、房间列表、进出房间都ok了;

而之前写的简易版server还只能接受连接和转发数据。

故,着手twisted(document)框架,把server搞起~

twisted启动一个tcp server很简单,按照示例几行代码就能搞定:监听端口、reactor启动事件检测/分发/回调执行、factory管理连接、改写protocol的事件处理函数(连接建立/连接丢失/读数据)。


=====================================================================================================================================================================

Scheduling tasks for the future
要在未来X秒的时候运行一个任务,这种方式定义在reactor接口里面twisted.internet.interfaces.IReactorTime:

from twisted.internet import reactor
def f(s):
    print "this will run 3.5 seconds after it was scheduled: %s" % s
reactor.callLater(3.5, f, "hello, world")
# f() will only be called if the event loop is started.
reactor.run()

如果函数结果很重要或者有必要处理函数抛出的异常,那么twisted.internet.task.deferLater功能可以很方便的创建一个Deferred并设置一个延迟调用:

from twisted.internet import task
from twisted.internet import reactor
def f(s):
    return "This will run 3.5 seconds after it was scheduled: %s" % s
d = task.deferLater(reactor, 3.5, f, "hello, world")
def called(result):
    print result
d.addCallback(called)
# f() will only be called if the event loop is started.
reactor.run()

如果我们要让一个任务在未来每X秒运行一次,我们可以用twisted.internet.task.LoopingCall:

from twisted.internet import task
from twisted.internet import reactor
def runEverySecond():
    print "a second has passed"
l = task.LoopingCall(runEverySecond)
l.start(1.0) # call every second
# l.stop() will stop the looping calls
reactor.run()

如果我们要取消一个已经在调度(we have scheduled)的任务(应该是指已经加入调度队列的任务):

from twisted.internet import reactor
def f():
    print "I'll never run."
callID = reactor.callLater(5, f)
callID.cancel()
reactor.run()

就像所有基于reactor的代码(reactor-based code)一样,为了让调度运行必须启动reactor: reactor.run().

=====================================================================================================================================================================

twisted.enterprise.adbapi: Twisted RDBMS support

Abstract

Twsited是一个异步网络框架,但是不幸的是大部分数据库API的实现都是阻塞接口---鉴于这个原因,twisted.enterprise.adbapi横空出世。它是针对标准DB-API 2.0 API的非阻塞接口,而且可以访问许多不同的RDBMSes.

Quick Overview

enterpreise.adbapi会在单独的线程里面执行阻塞的数据库操作,当数据库操作完成的时候又会在原来的线程(originating thread,可能是说twisted的主事件循环线程)中触发回调。在此期间,原来的线程可以继续做正常的工作,比如服务于其他请求。

How do I use adbapi?

我们并非要直接创建数据库连接,而是用adbapi.ConnectionPool类管理数据库连接。这东东允许enterprise.adbapi使用多个数据库连接,每个线程一个。很简单:

# Using the "dbmodule" from the previous example, create a ConnectionPool 
from twisted.enterprise import adbapi 
dbpool = adbapi.ConnectionPool("dbmodule", 'mydb', 'andrew', 'password')
需要注意的是:

不需要直接import dbmodule。只用把module的名字传递给adbapi.ConnectionPool的构造函数就行了。

需要传递给dbmodule.connect()的参数应当做额外的参数传递给adbapi.ConnectionPool的构造函数。关键字参数也没问题。

现在我们来做一个数据库查询:

# equivalent of cursor.execute(statement), return cursor.fetchall():
def getAge(user):
	return dbpool.runQuery("SELECT age FROM users WHERE name = ?", user)
def printResult(l):
	if l:
		print l[0][0], "years old"
	else:
		print "No such user"
除了getAge的返回值以外,其他都很直观。getAge返回了一个twisted.internet.defer.Deferred,这个Deferred允许在数据库操作完成的时候对于结果或者错误执行任意的callback。

除了runQuery,还有runOperation和runInteraction可以被调用(['CP_ARGS', '__doc__', '__getstate__', '__init__', '__module__', '__setstate__', '_close', '_reactor', '_runInteraction', '_runOperation', '_runQuery', '_runWithConnection', '_start', 'close', 'connargs', 'connect', 'connectionFactory', 'connections', 'connkw', 'dbapi', 'dbapiName', 'disconnect', 'finalClose', 'good_sql', 'max', 'min', 'name', 'noisy', 'openfun', 'reconnect', 'runInteraction', 'runOperation', 'runQuery', 'runWithConnection', 'running', 'shutdownID', 'start', 'startID', 'threadID', 'threadpool', 'transactionFactory'])。The function will be called in the thread with a twisted.enterprise.adbapi.Transaction, which basically mimics a DB-API cursor. 在所有情况下,一个数据库的事务会在这次数据库使用结束的时候提交,除非抛出了异常(如果抛出了异常就会回滚)。

def _getAge(txn, user):
	# this will run in a thread, we can use blocking calls
	txn.execute("SELECT * FROM foo")
	# ... other cursor commands called on txn ...
	txn.execute("SELECT age FROM users WHERE name = ?", user)
	result = txn.fetchall()
	if result:
		return result[0][0]
	else:
		return None
def getAge(user):
	return dbpool.runInteraction(_getAge, user)
def printResult(age):
	if age != None:
		print age, "years old"
	else:
		print "No such user"
getAge("joe").addCallback(printResult)
这里都假定了dbmodule使用"qmark"风格的参数(parmstyle)(参见DB-API说明书),这是不碍事的。如果我们的dbmodule要使用一种不同的参数风格(比如pyformat),那么直接用就是了。twisted没有试图提供任何类型的魔法参数转换---runQuery(query,aprams,...)直接映射到cursor.execute(query,params,...).

Examples of various database adapters

注意,第一个参数是我们要import和使用connect(...)的module的名字,接下来的参数是我们调用connect(...)的参数。

from twisted.enterprise import adbapi
# Gadfly
cp = adbapi.ConnectionPool("gadfly", "test", "/tmp/gadflyDB")
# PostgreSQL PyPgSQL
cp = adbapi.ConnectionPool("pyPgSQL.PgSQL", database="test")
# MySQL
cp = adbapi.ConnectionPool("MySQLdb", db="test")

=====================================================================================================================================================================

Using Threads in Twisted
Running code in a thread-safe manner

Twisted中的大部分代码非线程安全。例如,往一个protocol的transport写数据就不是线程安全的。所以,我们需要一种方式来把函数调度到主事件循环里面去运行。这个可用函数twisted.internet.interfaces.IReactorThreads.callFromThread来做到:

from twisted.internet import reactor
def notThreadSafe(x):
	 """do something that isn't thread-safe"""
	 # ...
def threadSafeScheduler():
	"""Run in thread-safe manner."""
	reactor.callFromThread(notThreadSafe, 3) # will run 'notThreadSafe(3)'
											 # in the event loop

Running code in threads

有时我们需要在线程中运行函数--例如,要访问阻塞的API。twisted用IReactorThreads API(twisted.internet.interfaces.IReactorThreads)提供这种方法。其他的功能性函数在twisted.internet.threads中提供。基本上,这些方法可以让我们把函数排进队列里让线程池运行了。

例如,要在一个线程中运行一个函数我们可以这样做:

from twisted.internet import reactor
def aSillyBlockingMethod(x):
	import time
	time.sleep(2)
	print x
# run method in thread
reactor.callInThread(aSillyBlockingMethod, "2 seconds have passed")
reactor.run()

Utility Methods

功能性函数(utility methods)不是twisted.internet.reactor APIs的一部分,而是在twisted.internet.threads中实现。

如果我们有多个函数要在一个线程中序列化运行,我们可以这么做:

from twisted.internet import reactor, threads
def aSillyBlockingMethodOne(x):
	import time
	time.sleep(2)
	print x
def aSillyBlockingMethodTwo(x):
	print x
# run both methods sequentially in a thread
commands = [(aSillyBlockingMethodOne, ["Calling First"], {})]
commands.append((aSillyBlockingMethodTwo, ["And the second"], {}))
threads.callMultipleInThread(commands)
reactor.run()

对于我们需要获取其结果的函数,我们可以让结果作为一个Deferred返回:

from twisted.internet import reactor, threads
def doLongCalculation():
    # .... do long calculation here ...
    return 3
def printResult(x):
    print x
# run method in thread and get result as defer.Deferred
d = threads.deferToThread(doLongCalculation)
d.addCallback(printResult)
reactor.run()
如果我们要在reactor线程中调用一个函数并获取它的结果,我们可以用blockingCallFromThread:
from twisted.internet import threads, reactor, defer
from twisted.web.client import getPage
from twisted.web.error import Error
def inThread():
	try:
		result = threads.blockingCallFromThread(
			reactor, getPage, "http://twistedmatrix.com/")
	except Error, exc:
		print exc
	else:
		print result
	reactor.callFromThread(reactor.stop)
reactor.callInThread(inThread)
reactor.run()
blockingCallFromThread将把传递给它的函数所返回的对象返回来、或者把传递给它的函数raise的异常raise出来。如果传给它的函数返回一个Deferred,那么它会返回Deferred的callback收到的数据或者raise Deferred的errback收到的异常( If the function passed to it returns a Deferred, it will return the value the Deferred is called back with or raise the exception it is errbacked with.

Managing the Thread Pool

线程池由twisted.python.threadpool.ThreadPool实现。

我们可能想修改threadpool的大小,增加或减少使用的线程的数量。我们可以很容易的做到:

from twisted.internet import reactor
reactor.suggestThreadPoolSize(30)
线程池的默认大小取决于所使用的reactor;默认的reactor使用的线程池最小线程数为5最大线程数为10.在修改线程池大小之前一定要小心并且要保证已经理解线程以及线程对资源的使用。

=====================================================================================================================================================================

Deferred Reference
Defrereds

twisted用Deferred对象管理回调序列。客户程序把一系列的函数附加到deferred上,当异步请求的结果可用时这些函数会被调用,这一系列的函数就是回调(callback),或者说回调链(callback chain).如果异步请求发生错误也会有一系列的函数被调用,这些函数就是错误回调(errback)或者错误回调链(errback chain)。当结果可用时异步库的代码调用第一个callback,或者当错误发生时调用第一个errback,Deferred对象会把每个callback/errback函数的结果hold住并传入链中的下一个函数

Callbacks

twisted.internet.defer.Deferred对象是函数将会在某个点上产生结果的承诺。我们可以把callback函数附加到给一个Deferred,一旦Deferred得到了结果这些callbacks就会被执行。

Errbacks

如果不显式的return a twisted.python.failure.Failure或者raise an exception,错误会停止传播,正常的callbacks会从这个点上继续执行(详情参见页面上的链路图,一个callback跟一个errback对应,每个callback(errback)的结果传递给下一个callback/errback,同级的errback(callback)没机会执行了),使用errback返回的结果。如果确实return a twisted.python.failure.Failure或者raise an exception,那么错误会传递给下一个errback.

如果一个errback不返回任何东西,就等效于返回了一个None对象,那么意味着callbacks会在这errback之后继续执行。callback会用None作为参数,所以要小心!

twisted.python.failure.Failure实例有一个有用的函数叫trap,可用于作如下等效行为:

try:
    # code that may throw an exception
    cookSpamAndEggs()
except (SpamException, EggException):
    # Handle SpamExceptions and EggExceptions
    ...

等效于

def errorHandler(failure):
    failure.trap(SpamException, EggException)
    # Handle SpamExceptions and EggExceptions
d.addCallback(cookSpamAndEggs)
d.addErrback(errorHandler)

如果传递给trap的参数跟Failure中封装的错误不匹配,trap会re-raise那个错误。

twisted.internet.defer.Deferred.addCallbacks()

# Case 1
d = getDeferredFromSomewhere()
d.addCallback(callback1)       # A
d.addErrback(errback1)         # B
d.addCallback(callback2)       
d.addErrback(errback2)        
# Case 2
d = getDeferredFromSomewhere()
d.addCallbacks(callback1, errback1)  # C
d.addCallbacks(callback2, errback2)

如果callback1中发生错误,Case 1中errback1会被调用来处理Failure;Case 2中errback2会被调用来处理Failure。

在Case 1中,line A的callback会处理getDeferredFromSomewhere中的成功结果,line B的errback会处理上流代码(the upstream source)中的错误或者A中的错误。

在Case 2中,line C的errback只处理getDeferredFromSomewhere中抛出的错误,不会处理callback1中抛出的错误。

Unhandled Errors

对于没有添加errback而发生错误的情况,Deferred被垃圾回收的时候,twisted会把错误的调用栈(traceback)打印到日志文件。所以要小心,如果我们保留了一个对Deferred的引用,从而阻止了它被垃圾回收,那么我们永远都看不到这个错误(并且会发现我们的callback很神秘的从不被调用)。如果不确定是否有errback忘写,可以显式的在我们callbacks后面追加一个如下所示的errback:

from twisted.python import log
d.addErrback(log.err)
Handling either synchronous or asynchronous results

有些应用程序中,函数要嘛是异步的(返回一个Deferred,当数据到达时fire这个Deferred)要嘛是同步的(立马返回一个结果)。不过,也有些函数需要同时接受立即结果和Deferreds.

同步函数:

def authenticateUser(isValidUser, user):
    if isValidUser(user):
        print "User is authenticated"
    else:
        print "User is not authenticated"
异步函数:
from twisted.internet import reactor, defer
def asynchronousIsValidUser(user):
    d = defer.Deferred()
    reactor.callLater(2, d.callback, user in ["Alice", "Angus", "Agnes"])
    return d
twisted.internet.defer.maybeDeferred()可以保证函数的返回值是一个Deferred,即使函数是一个同步函数:
from twisted.internet import defer
def printResult(result):
    if result:
        print "User is authenticated"
    else:
        print "User is not authenticated"
def authenticateUser(isValidUser, user):
    d = defer.maybeDeferred(isValidUser, user)
    d.addCallback(printResult)

isValidUser是synchronousIsValidUser或者asynchronousIsValidUser都行。

当然,我们也可以修改synchronousIsValidUser使其返回一个Deferred.(https://twistedmatrix.com/documents/current/core/howto/gendefer.html)

DeferredList

有时候我们需要在几个不同的事件都发生了才被通知,而不是分别等待每个事件。例如,我们可能需要等待一个列表中的连接全部关闭。 twisted.internet.defer.DeferredList就是干这个的。

根据多个Deferred来创建一个DeferredList,我们只需简单的把一个存储着目标Deferred的list传递进去就行了:

dl = defer.DeferredList([deferred1,deferred2,deferred3])

现在我们可以把DeferredList当成一个普通的Deferred对待;我们可以调用addCallbacks等方法。DeferredList会在所有Deferreds完成的时候调用它的callback. callback会以DeferredList包含的所有Deferred的结果构成的list作为参数进行执行,like so:

# A callback that unpacks and prints the results of a DeferredList
def printResult(result):
    for (success, value) in result:
        if success:
            print 'Success:', value
        else:
            print 'Failure:', value.getErrorMessage()

# Create three deferreds.
deferred1 = defer.Deferred()
deferred2 = defer.Deferred()
deferred3 = defer.Deferred()

# Pack them into a DeferredList
dl = defer.DeferredList([deferred1, deferred2, deferred3], consumeErrors=True)

# Add our callback
dl.addCallback(printResult)

# Fire our three deferreds with various values.
deferred1.callback('one')
deferred2.errback(Exception('bang!'))
deferred3.callback('three')

# At this point, dl will fire its callback, printing:
#    Success: one
#    Failure: bang!
#    Success: three
# (note that defer.SUCCESS == True, and defer.FAILURE == False)
标准的DeferredList不会调用errback,但是传给DeferredList的Deferred发生的failure还是会调用errback除非DeferredList的consumeErrors被设成了True(这句话很绕,我理解的意思是,DeferredList只会等待所有Deferred的异步数据到大后才会执行callback,但是不会执行errback;而构成DeferredList的Deferred发生错误时还是可以调用自己的errback;要想阻止Deferred调用自己的errback,需要在构建DeferredList时设置参数consumeError=True.这个理解需要回头用程序验证一下!!)。

如果要为构成DeferredList的独立的Deffered提供callback,我们应该对于这些callback添加的时间点很小心。添加一个Deferred到DeferedList的行为实际上内部为Deferred插入了一个callback(当这个callback执行的时候,它检查DeferredList是否已经完成了(我理解的意思是,这个callback检测自己是否是最后一个完成的Deferred,如果是的话就可以调用DefferredList的callback了)).要记住的很重要的事情是,这个内部自动加入的callback它记录的值会是作为result list传入给DefferedList callback函数的东东。

因此,如果我们在把Deferred加入到DeferredList之后再添加一个callback给Deferred,那么这个callback返回的值将不会传给DeferredList的callback(擦,这么说的话,DeferredList不但要等待它内部所有Deferred的数据到达,而且还要等所有Deferred的callback执行完成,然后把最后的结果合一起作为DeferredList等待的数据). 为了避免困惑,我们建议不要在Deferred添加到DeferredList之后再为Deferred添加callback.

def printResult(result):
    print result
def addTen(result):
    return result + " ten"

# Deferred gets callback before DeferredList is created
deferred1 = defer.Deferred()
deferred2 = defer.Deferred()
deferred1.addCallback(addTen)
dl = defer.DeferredList([deferred1, deferred2])
dl.addCallback(printResult)
deferred1.callback("one") # fires addTen, checks DeferredList, stores "one ten"
deferred2.callback("two")
# At this point, dl will fire its callback, printing:
#     [(1, 'one ten'), (1, 'two')]

# Deferred gets callback after DeferredList is created
deferred1 = defer.Deferred()
deferred2 = defer.Deferred()
dl = defer.DeferredList([deferred1, deferred2])
deferred1.addCallback(addTen) # will run *after* DeferredList gets its value
dl.addCallback(printResult)
deferred1.callback("one") # checks DeferredList, stores "one", fires addTen
deferred2.callback("two")
# At this point, dl will fire its callback, printing:
#     [(1, 'one), (1, 'two')]
Other behaviours

fireOnOneCallback DeferredList会在有Deferred调用callback时立马调用自己的callback,然后DeferredList不做任何事儿了,忽略其他还没完成的Deferred

fireOnOneErrback DeferredList会在有Deferred调用errback时立马调用自己的errback,然后DeferredList不做任何事儿了,忽略其他还没完成的Deferred

consumeErrors 阻止错误在DeferredList包含的任何Deferred的链路上传播(普通创建的DeferredList不会影响结果在它的Deferred的callbacks和errbacks中传递)。用这个选项在DeferedList中停止错误,将阻止“Unhandled error in Deferred”警告出现在DeferredList包含的没必要添加额外errbacks的Deferred中。设置consumeErrors参数为true不会改变fireOnOneCallback或者fireOnOneErrback.

gatherResults

DeferredList的一个常见应用是join若干平行的异步操作,如果所有操作都成功就成功完成,如果任何一个操作失败就失败。这种情况下,twisted.internet.defer.gatherResults是一个有用的捷径:

from twisted.internet import defer
d1 = defer.Deferred()
d2 = defer.Deferred()
d = defer.gatherResults([d1, d2], consumeErrors=True)
def printResult(result):
    print result
d.addCallback(printResult)
d1.callback("one")
# nothing is printed yet; d is still awaiting completion of d2
d2.callback("two")
# printResult prints ["one", "two"]

consumeErrors参数跟DeferredList有一样的意义:如果为true,gatherResults会消耗掉传进来的Deferred的所有错误。这个参数都设为true吧,除非我们要为传进来的Deferred添加callbacks或者errbacks,或者除非我们知道他们不会失败。如果不设为true,失败会导致twisted记录一个unhandled error到日志里。这个参数从twisted 11.1.0之后有效。

inlineCallbacks(2014.5.29)

https://twistedmatrix.com/documents/14.0.0/api/twisted.internet.defer.html#inlineCallbacks

这个inlineCallbacks只是把Deferred表现的像序列表程序一样,当Deferred的结果(异步)返回了twisted会回到yield的位置把程序唤醒。只是让异步程序看上去好像同步程序一样,让代码结构更易读和简单。

今天在twisted中用到redis,txredis是不错的选择,可以按照twisted的异步模式写逻辑(就像处理其他网络连接或者数据库操作一样),pypi上的连接09年之后就没更新了,很多redis的特性(比如lpush,pubsub)都不支持,需要到这里拿最新的。

redis-py呢,需要自己把阻塞调用切到其他线程去,完事再切换来,太麻烦了

from twisted.internet import reactor
from twisted.internet import protocol
from twisted.internet import defer
from txredis.protocol import Redis
# Hostname and Port number of a redis server
HOST = 'localhost'
PORT = 6379
@defer.inlineCallbacks
def main():
    clientCreator = protocol.ClientCreator(reactor, Redis)
    redis = yield clientCreator.connectTCP(HOST, PORT)
    res = yield redis.ping()
    print res
    info = yield redis.info()
    print info
    res = yield redis.set('test', 42)
    print res
    test = yield redis.get('test')
    print test
if __name__ == "__main__":
    main()
    reactor.run()

=====================================================================================================================================================================

parse tmx(tile map xml) file

跟twisted没必然关系,只是最近在制作一个小手游来练习twisted框架使用,而这个游戏有用到瓦片地图;client端2dx自带tmx地图的解析和加载,server端需要自己实现。

tmx format: https://github.com/bjorn/tiled/wiki/TMX-Map-Format

tmxlib(PyPI): http://pytmxlib.readthedocs.org/en/v0.2.0/ (If you’re looking to use maps in a game, chances are tmxlib won’t help you much. Try pytmxloader, PyTMX, or one of the other projects listed on the Tiled wiki.)

PyTMX: https://github.com/bitcraft/PyTMX (installation requires pygame)

pytmxloader: https://code.google.com/p/pytmxloader/ (installation requires pygame)

我选用的是tmxlib,原因是两外两个需要pygame. 虽然tmxlib的文档中有推荐在游戏中使用另两个库,但是我觉得它的意思是client端,而不是server.

地图解析

[dongsong@localhost python_study]$ cat tmxlib_test.py 
#encoding=utf-8
import tmxlib
from pprint import pprint


def LoadMap1(filename):
        map = tmxlib.Map.open(filename)
        print 'Map.open() returned: ', map
        return map


def LoadMap2(filename):
        data = file(filename,'rb').read()
        map = tmxlib.Map.load(data)
        print 'Map.load() returned: ',map
        return map


def SaveMap(map, filename):
        map.save(filename)
        print 'map %s saved to "%s"' % (str(map),filename)


if __name__ == '__main__':
        LoadMap1('robot_1.tmx')
        map = LoadMap2('robot_1.tmx')
        SaveMap(map,'robot_2.tmx')
        print 


        print 'map.tilesets = ',map.tilesets
        tileset = map.tilesets[0]
        print 'tileset = map.tilesets[0], len(tileset) = ', len(tileset), '"'
        for indexId in range(len(tileset)):
                print '\ttileset[%d] = ' % indexId, tileset[indexId]
        try:
                del map.tilesets['tilemap']
        except Exception,e:
                print e
        tile = tileset[1]
        print 'tile = tileset[1] : ', tile
        print 'tile.gid(map) = ', tile.gid(map)
        print


        print 'len(map.layers) = ', len(map.layers), ':'
        for layer in map.layers:
                print '\t',layer
        map.add_layer('Sky')
        map.add_layer('Underground', before = 'Sky')
        print 'len(map.layers) = ', len(map.layers), ':'
        for layer in map.layers:
                print '\t',layer
        print


        layer = map.layers['Tiles1']
        print 'layer = map.layers["Tiles1"] : ', layer
        print 'layer[0,0] = ', layer[0,0]
        print 'layer[1,3] = ', layer[1,3]
        print 'layer[1,5] = ', layer[1,5]
        print 'layer[1,6] = ', layer[1,6]
        tile = layer[0,0]
        print 'tile = layer[0,0] "', tile
        for attr in dir(tile):
                if not attr.startswith('__'):
                        print '\ttile.%s = '%attr, getattr(tile,attr)
        print 


        layer = map.layers['Objects1']
        print 'layer = map.layers["Objects1"] : ', layer
        for ob in layer.all_objects():
                print ob
                pprint(ob.to_dict())
[dongsong@localhost python_study]$ vpython tmxlib_test.py 
Map.open() returned:  <tmxlib.map.Map object at 0x1610a50>
Map.load() returned:  <tmxlib.map.Map object at 0x1610d50>
map <tmxlib.map.Map object at 0x1610d50> saved to "robot_2.tmx"


map.tilesets =  [<ImageTileset 'tilemap' at 0x162c050>]
tileset = map.tilesets[0], len(tileset) =  2 "
        tileset[0] =  <TilesetTile #0 of tilemap at 0x162c290>
        tileset[1] =  <TilesetTile #1 of tilemap at 0x162c2d0>
Cannot remove <ImageTileset 'tilemap' at 0x162c050>: map contains its tiles
tile = tileset[1] :  <TilesetTile #1 of tilemap at 0x162c2d0>
tile.gid(map) =  2


len(map.layers) =  2 :
        <TileLayer #0: 'Tiles1' at 0x162c0d0>
        <ObjectLayer #1: 'Objects1' at 0x162c150>
len(map.layers) =  4 :
        <TileLayer #0: 'Tiles1' at 0x162c0d0>
        <ObjectLayer #1: 'Objects1' at 0x162c150>
        <TileLayer #2: 'Underground' at 0x162c190>
        <TileLayer #3: 'Sky' at 0x162c310>


layer = map.layers["Tiles1"] :  <TileLayer #0: 'Tiles1' at 0x162c0d0>
layer[0,0] =  <MapTile (0, 0) on Tiles1, gid=1  at 0x162c250>
layer[1,3] =  <MapTile (1, 3) on Tiles1, gid=2  at 0x162c250>
layer[1,5] =  <MapTile (1, 5) on Tiles1, gid=0  at 0x162c250>
layer[1,6] =  <MapTile (1, 6) on Tiles1, gid=2  at 0x162c250>
tile = layer[0,0] " <MapTile (0, 0) on Tiles1, gid=1  at 0x162c250>
        tile._TileLikeObject__mask_property =  <bound method MapTile.__mask_property of <MapTile (0, 0) on Tiles1, gid=1  at 0x162c250>>
        tile._pos =  (0, 0)
        tile._tileset_tile =  <bound method MapTile._tileset_tile of <MapTile (0, 0) on Tiles1, gid=1  at 0x162c250>>
        tile._value =  1
        tile._wrap_coords =  <bound method MapTile._wrap_coords of <MapTile (0, 0) on Tiles1, gid=1  at 0x162c250>>
        tile.flipped_diagonally =  False
        tile.flipped_horizontally =  False
        tile.flipped_vertically =  False
        tile.get_pixel =  <bound method MapTile.get_pixel of <MapTile (0, 0) on Tiles1, gid=1  at 0x162c250>>
        tile.gid =  1
        tile.height =  1.0
        tile.hflip =  <bound method MapTile.hflip of <MapTile (0, 0) on Tiles1, gid=1  at 0x162c250>>
        tile.image =  <tmxlib.image_base.ImageRegion object at 0x162c490>
        tile.layer =  <TileLayer #0: 'Tiles1' at 0x162c0d0>
        tile.map =  <tmxlib.map.Map object at 0x1610d50>
        tile.number =  0
        tile.pixel_height =  16
        tile.pixel_pos =  (0, 16)
        tile.pixel_size =  (16, 16)
        tile.pixel_width =  16
        tile.pixel_x =  0
        tile.pixel_y =  16
        tile.pos =  (0, 0)
        tile.properties =  {}
        tile.rotate =  <bound method MapTile.rotate of <MapTile (0, 0) on Tiles1, gid=1  at 0x162c250>>
        tile.size =  (1.0, 1.0)
        tile.tile_to_image_coordinates =  <bound method MapTile.tile_to_image_coordinates of <MapTile (0, 0) on Tiles1, gid=1  at 0x162c250>>
        tile.tileset =  <ImageTileset 'tilemap' at 0x162c050>
        tile.tileset_tile =  <TilesetTile #0 of tilemap at 0x162c290>
        tile.value =  1
        tile.vflip =  <bound method MapTile.vflip of <MapTile (0, 0) on Tiles1, gid=1  at 0x162c250>>
        tile.width =  1.0
        tile.x =  0
        tile.y =  0


layer = map.layers["Objects1"] :  <ObjectLayer #1: 'Objects1' at 0x162c150>
<tmxlib.mapobject.RectangleObject object at 0x162c1d0>
{'height': 16,
 'name': u'man',
 'properties': {'roleId': '1'},
 'type': '',
 'visible': True,
 'width': 16,
 'x': 16,
 'y': 16}
<tmxlib.mapobject.RectangleObject object at 0x162c210>
{'height': 16,
 'name': u'monster',
 'properties': {},
 'type': '',
 'visible': True,
 'width': 16,
 'x': 96,
 'y': 112}
地图展示



杂记:

1.如何主动断开连接(2014.6.13) http://mumak.net/stuff/twisted-disconnect.html

To make the server stop listening:
    port = reactor.listenTCP(portNumber, factory)
    ...
    port.stopListening()
You can effect the last two with one stroke, using either of the following commands:
    protocol.transport.loseConnection()
OR
    connector = reactor.connectTCP(host, port, factory)
    ...
    connector.disconnect()

2.关于last_insert_id()和多数据库操作一起执行的问题(有些是为了保证事务,有些是为了避免每个sql都写异步逻辑的复杂性)

https://twistedmatrix.com/documents/current/api/twisted.enterprise.adbapi.ConnectionPool.html

def vouch_gift():
    log.msg('to set vouch gifts')
    global channels
    g_mysql.runQuery('truncate tbl_vouch_gift_time')
    g_mysql.runQuery('truncate tbl_vouch_gift')
    g_mysql.runQuery('truncate tbl_vouch_gift_price')
    for _ in range(1, random.randint(1,10)):
        INTERVAL = 4*24*3600 #sec
        startTime = rand_start_time(INTERVAL)
        endTime = startTime + random.randint(0,INTERVAL*2)
        def myRtFunc(rt):
            log.msg('insert a vouch gift time config: %s' % pprint.pformat(rt))
        def myfunc(tx):
            rt = {'timeId':0, 'gifts':[]}
            tx.execute('insert into tbl_vouch_gift_time(name,startTime,endTime) values ("%s", %s, %s)' % (rand_string(), startTime, endTime))
            tx.execute('select last_insert_id()')
            timeId = int(tx.fetchone()[0])
            rt['timeId'] = timeId
            for _ in range(1,random.randint(1,110)):
                amount = random.randint(100,1000000)/100.0
                limit = random.randint(0,1)==0 and -1 or random.randint(-1,300)
                tx.execute('insert into tbl_vouch_gift(timeId,name,timesPerRole,resource,items,verifyGolds) values(%s,"%s",%s,"%s","%s",%s)'%(timeId,rand_string(),limit,rand_res_str(),rand_item_str(),amount*50))
                tx.execute('select last_insert_id();')
                giftId = int(tx.fetchone()[0])
                rt['gifts'].append(giftId)
                for ch in channels:
                    tx.execute('insert into tbl_vouch_gift_price(id,channel,appStoreId,amount) values(%s,"%s","%s",%f)'%(giftId,ch,rand_string(),amount))
            return rt
        g_mysql.runInteraction(myfunc).addCallback(myRtFunc)
    SendCmdToGame('@{fun="setVouchGift",params={}}')


©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页