2008年2月27日星期三

Immediate Mode GUI

今晚主要在思考 GUI 的問題。因為之前看 mono 提及 Unity 這個引擎,便去看 Unity 對 GUI 的部份,參考它支持甚麼功能和如何跟 Script 結合。

一看之下,竟然是這種奇異的寫法:
function OnGUI () {
if (GUI.Button (Rect (10,10,150,100), "I am a button")) {
print ("You clicked the button!");
}
}
其原理是,Unity 在一個 frame 裡會執行這個函式最多兩次,第一次 GUI.Button() 會渲染那個按鈕,第二次是處理輸入,如果GUI.Button()被按下,就傳回 true。

後來再在網上找到了相關的討論及短片(我無看足全程),稱傳統的 GUI 系統為 Retained Mode (下簡稱RM),這種系統為 Immediate Mode (下簡稱IM)。

傳統的 RM 做法是建立 GUI 物件的 Hierarchy,之後當有輸入時就通知應用程式 (event-driven)。而 IM 就不需要建立物件,純粹以 procedural 的運行方式顯示及處理反饋。

乍看之下,IM 的函式非常簡單,亦不需要額外的記憶體,好像是一個很好的方案。但再想了一會,相對 RM 的做法好像沒有那麼好:
  1. 其實 IM 和 RM 都要用一定的內存。如果 RM 是用 Script 或描述檔案 (e.g. XML) 來產生 GUI 物件,那段 Script 或描述檔案 在產生 GUI 物件之後就可以釋放,而它所描述的訊息只是轉化為內存的物件吧。所以兩種方法也是包含同樣的資訊,只是形式不同。
  2. IM 的 GUI 是 Stateless 的。因此一些需要 state 的 GUI 物件需要由函式外傳入及取回,例如
    textFieldString = GUI.TextField (Rect (25, 25, 100, 30), textFieldString);
  3. IM 執行效率應比一般的 RM 低。渲染 GUI 是每個 frame 都要處理的,而發生輸入事件的頻率相對非常低。IM 需要每個 frame 運行 Script,而 RM 可以在 C++ 獨立處理 GUI Hierarchy 的渲染,並且處理輸入後才產生 event 通知 Script。但如果 IM 的代碼也是用 C++ 寫的話,情況可能會相反,因為所有 GUI call 都是 static binding,而一般 RM 的做法都需要 virtual function call (例如渲染的函式)。
  4. 要做 IM GUI 的編輯工具比較困難。可能要用 RM 的資料去生成 IM 的代碼,但這樣做的話,連 event handling 的代碼也要從 RM 的資料插進去 IM ,那麼,IM 就好像沒有意義了。這可能就是 Unity 沒有 GUI 編輯工具的原因。
  5. 和第 4 點相關,RM 把物件的狀態描述和行為分離,減低 coupling。而 IM 反而應為兩者結合在一起編程比較容易。這讓我想起用 C 寫 CGI 和用 XML/XSLT 做網頁的日子......
原來這種 IM 模式至少在 2005 年已有人提倡,我真的是孤陋寡聞。

P.S. 今天無代碼上的進展......

2 則留言:

Ricky 說...

好高興有一點新的東西沖擊下哩.
訓身做 (正確 D 應該係 維護至真) 同一個 Project 太長時間無洗創意.

1) 很難說...VM 用的內存很難估計. 不過物件用的內存一定遠遠多過句 string.
2) 可以是好處, 因為唔洗另外同 application syn state. 不過像 ScrollBar 可以如何哩.
3) 如果 IM 是用 C++ 寫的話, memory locatitly 會很好, 令效率有可能勝過 RM.
4) 同意. 但 GUI 最終也要寫 Logic.
5) 同意, 有點 back to C from OOP, 旦不一定是壞事.

有個疑問: Radio button grouping 可以如何?

還有... 你知道什麼是 Pull XML parser 嗎? Luicd2 正使用它哩.
有了 Pull API, SAX 也可以輕易 build on top of it.

Milo 說...

Radio button 和其他需要 hierarchy 的物件都可以用 grouping 來實現。例如:

BeginRadioButtonGroup()
r1 = RadioButton(pos1, "R1", r1)
r2 = RadioButton(pos2, "R2", r2)
EndRadioButtonGroup()

但 radio button 最簡單就是用一個control 放齊可選擇的 button,例如

sel = RadioButtonGroup(pos, { "R1", "R2" }, sel)

ScrollBar 只有一個 value state,和 textbox 一樣。

你告訴我才知道 Pull XML,好像比 event-driven 的 SAX 更有彈性。最少解決你之前要用 multi-thread 的 consumer-producer 形式來讀 XML。

但是,相對 SAX 有缺點嗎? 有沒有 C/C++ implementation?

P.S. 我這陣子也有想 serialization 的問題。想在 tools 用 C# 的 XML/binary serialization,之後轉作執行時期的版本。曾經有個想法是用 Lua 做執行時期的 serialization 版本。不過還未詳細研究。