顯示具有 event 標籤的文章。 顯示所有文章
顯示具有 event 標籤的文章。 顯示所有文章

2010年2月17日 星期三

聊天室練習 - VIII. 事件驅動

事件驅動的優缺點於先前的文章已經描述過,在此就不再重複。僅以一個簡單的測試來檢驗啟動事件與事件執行的順序。
在ChatRoomMain.py,第130行之後插入
print 'return to CommandParser'
另外,在ChatRoomFrame.py,第81行之後插入
print 'enter event_TEXTDISPLAY handle'

當輸入對談訊息後,ChatRoomMain.CommandParser啟動事件event_TEXTDISPLAY,對應的事件執行在ChatRoomFrame.CmdTextDisplayHandle。列印出的訊息如下:
enter event_TEXTDISPLAY handle
return to CommandParser
enter event_TEXTDISPLAY handle
return to CommandParser
enter event_TEXTDISPLAY handle
return to CommandParser
enter event_TEXTDISPLAY handle
return to CommandParser
enter event_TEXTDISPLAY handle
return to CommandParser

從列印出的訊息可以看出,每次啟動事件(ChatRoomMain.py,第130行)後,總是先處理事件執行(ChatRoomFrame.py,第81 行),執行完後才再回到啟動事件處繼續執行(ChatRoomMain.py,第131行,print 'return to CommandParser')。猜測啟動事件後的事件執行並不會獨立出一個thread來處理,而是如同函數呼叫般直接跳至call back function。因此ChatRoomMain.py,第130行
messenger.send(event_TEXTDISPLAY,[cmdItem['i'], cmdItem['param']])
可以看成
self.ChatRoomFrame.CmdTextDisplayHandle(cmdItem['i'], cmdItem['param'])

在聊天室練習中,由於環境較單純,使用事件驅動似乎沒得到明顯的好處。
假設一個遊戲過程中,不同階段都有不同的處理對談文字顯示的函數。在不使用事件驅動的方式下,呼叫端(例如此練習中的CommandParser)必須要以階段來判斷該執行哪個文字顯示函數,並且當文字顯示函數更動名稱時,例如改變函數名稱或是換到不同類別內,呼叫端也要跟著更新。
但若使用事件驅動的方式,呼叫文字顯示與執行文字顯示的函數間以一個事件名稱(例如此練習中的event_TEXTDISPLAY)當介面連接,只要執行文字顯示的函數綁到此事件名稱的call back function,就可以在不更動呼叫端程式碼的情形下改變執行文字顯示的函數。因此,使用事件驅動應該可以減少程式維護上的成本。

為了避免事件名稱的衝突,在此練習中統一將事件名稱定義在CommandInputLib.py的最後。



----
  1. 操作方式
  2. 程式架構
  3. 網路設定、接收與傳送
  4. Server - Client 間的傳輸協定
  5. 傳輸命令格式
  6. GUI
  7. 中文輸入與顯示
  8. 事件驅動

2010年2月3日 星期三

多個event handler接收同一個event的執行結果

panda3d的event可以參考
http://www.panda3d.org/wiki/index.php/Event_Handlers
以下例子是測試多個event handler接收同一個event的執行結果。

from direct.showbase import DirectObject
import direct.directbase.DirectStart

class EventHandler(DirectObject.DirectObject):
    def __init__(self,id):
        self.accept('test',self.eventHandle)
        self.id = id
    def eventHandle(self):
        print "eventHandle in EventHandler(%d)"%(self.id)

objectList = [EventHandler(i) for i in xrange(10)]
messenger.send('test')
run()

執行結果如下:

eventHandle in EventHandler(9)
eventHandle in EventHandler(8)
eventHandle in EventHandler(2)
eventHandle in EventHandler(7)
eventHandle in EventHandler(1)
eventHandle in EventHandler(6)
eventHandle in EventHandler(0)
eventHandle in EventHandler(5)
eventHandle in EventHandler(4)
eventHandle in EventHandler(3)

先產生10個不同的EventHandler物件,它們接收同一個event 'test'。
以messenger.send('test')發出event 'test'之後,這10個EventHandler物件都收到這個event,並執行各自的call back function。
從執行結果可以看到這10個EventHandler物件並不是依照被產生的順序接收到event,但每次執行的順序相同。

用event的方式可以分離產生event的物件,與執行此event的call back function物件。產生event的物件不必知道call back function函數名稱(參照),執行call back function的物件可以在執行期動態的指定(accept)或取消(ignore)對應event name的call back function,而不用改變產生event的物件。
缺點也在於它的優點,event機制如同全域變數,當兩個產生event的物件誤用了相同的event name時,會呼叫到其他接收相同event name的call back function,造成意外的錯誤。另外一個缺點在於,無法輕易的從產生event的地方得知此event的call back function為何,它可能藏在任何物件中,或是不存在。

2010年1月1日 星期五

利用python的執行期建立函數功能簡化Keyboard event宣告

Panda3D的Keyboard event可以從下列網址參考
主要是用self.accept( event_name, function ) 將event與function連結起來。
以下是一個例子:
import direct.directbase.DirectStart
from direct.showbase.DirectObject import DirectObject

class keyHandler(DirectObject):
    def __init__(self):
        self.CallBackFuncRefMap = {}
        self.accept('a', self.KeyHandle_a)

    def AddKeyHandle(self, keyString, funcRef):
        self.CallBackFuncRefMap[keyString] = funcRef

    def KeyHandle_a(self):
        self.CallBackFuncRefMap['a']()


# testing
def callBack_a():
    print "exec: callBack_a"

test_keyHandler = keyHandler()
# bind callBack_a with keyboard event-'a'
test_keyHandler.AddKeyHandle('a', callBack_a)

run()
self.accept('a', self.KeyHandle_a)將鍵盤事件'a'與KeyHandle_a連結。
self.CallBackFuncRefMap是一個call back函數(call back函數不一定會宣告在keyHandler類別內)的hash table,以鍵盤事件名稱為key。經由AddKeyHandle函數新增此hash table的內容,如此便可在self.KeyHandle_a內呼叫。
新增鍵盤事件時,除了增加self.accept( event_name, function )之外,對應於event_name的function也得新增,如同上面的self.accept('a', self.KeyHandle_a)與KeyHandle_a,然而對應的function的內容有很大部分是相同的。因此可利用python於runtime建立函數的功能,將keyHandler類別改寫如下:
class keyHandler(DirectObject):
    def __init__(self):
        self.CallBackFuncRefMap = {}

    def AddKeyHandle(self, keyString, funcRef):
        self.CallBackFuncRefMap[keyString] = funcRef
        def callBackFunc():
            self.CallBackFuncRefMap[keyString]()
        self.accept(keyString, callBackFunc)
於AddKeyHandle函數內建立callBackFunc,並執行self.accept(keyString, callBackFunc)完成鍵盤事件與call back函數的連結。如此新增鍵盤事件只要呼叫AddKeyHandle,而不用在keyHandler類別內新增對程式碼了。