2011-02-21

Eclipse 的 Google Plugin 及 GWT 2.2 版問世

原文網址:http://googlewebtoolkit.blogspot.com/2011/02/google-plugin-for-eclipse-and-gwt-22.html

我們很高興在這裡分享 Eclipse 的 Google Plugin(以下簡稱 GPE)和 GWT 2.2 版 提供的幾個新功能。首先,GPE 把 GWT Designer 這個強大的 WYSISYG Ajax UI 設計工具整合進來。這可以讓你更簡單快速地建立 UI。其二,GWT SDK 支援初步的 HTML5 規格,讓開發者獲得現代 web 的優勢。此外,GWT 的 CellTable widget 現在提供了新的功能,可以設定預設的排序欄位、設定欄位寬度等。這些新的特性可以讓你用 Java 工具與 Eclipse 更簡單地做出超優的 web application。雖然這些 application 可以在任何平台上運作,不過 GPE 對於 Google App Engine 上的 deploy 與執行更為方便。

安裝 Eclipse 的 Google Plugin 以及 GWT SDK 的介紹可以在 GPE 入門裡頭找到。

如果只是單純想找 GWT 2.2 SDK,就在這裡頭

GWT Designer
將 GWT Designer 直接整合進 Eclipse 的 Google Plugin 中,是過去幾個月的首要工作。我們從社群中得到很積極的意見回饋,在這個版本當中,我們不只要提供 GWT Designer 的最佳開發經驗,也要讓 GWT Designer 與 GPE 整合地天衣無縫。


HTML5 的功能
GWT 2.2 版支援特定的 HTML5 功能,例如 Canvas 可以讓你用 script 動態繪製 2D 圖形或 bitmap 圖,以及嵌入影音的 tag。這些 API 仍在實驗中,有可能在接下來的幾個版本中會有所改變,但我們覺得已經穩定到可以提供一些實質好處。下面式一個 GWT 團隊成員做的一個 demo,展示 GWT SDK 中對 Canvas 的支援。你可以在這裡找到這個 demo 的程式碼: http://code.google.com/p/gwtcanvasdemo/

新的 CellTable API
在 GWT 2.1 版時,我們發現很多開發者常常在案子裡使用了 CellTable 這個 widget,然後馬上就貼上同樣的程式碼好加上 sort 功能,接著就不辭辛苦地設定欄位的寬度。到了 GWT 2.2 版,這些功能已經變成 CellTable 本身的一部分啦。我們可以、也想要加強原生的 GWT widget,增添功能以盡可能減少開發者必須自己撰寫的程式碼。
如果你想實際看這些更新的話,GWT Showcase 的 CellTable 範例裡頭有。

關於 Java 1.5
GWT 2.2 版將(僅)廢止 Java 1.5 的支援,這會導致 build application 時會出現警告訊息。 雖然 Java 1.5 仍然可以搭配這個版本的 GWT,開發者還是應該升級 Java 來消除這些警告、並確保未來 GWT 版本的相容性。

我們很樂意聽到你所提出的問題或是意見回饋,可以的話就到 Google Web Toolkit Group 來暢所欲言吧!

2011-02-01

GWT 不適合開發 HTML5 上的遊戲

原文網址:http://my2iu.blogspot.com/2011/01/gwt-isnt-good-environment-for-html5.html

譯者註:這篇並沒有寫的很好,甚至後兩段已經脫離主題,變成是在思考 JavaScript 的 coding style 與 IDE 協助開發的問題。不過頭幾段倒是給我們一些關於 GWT  browser plugin 的運作原理與問題,故潦草一翻,順便當成是腦力復健 [慘笑]


去年我在 XNA 上頭寫了一個小遊戲,因為沒人玩,所以我開始把它轉換成使用 HTML5 特性的網頁遊戲。最初遊戲是用 C# 寫的,若要將它網頁化,我覺得最簡單的方法是用 Java 改寫,然後用 GWT 轉成 JavaScript。GWT 是一組 Google 提供的工具,讓你可以用 Java 寫網頁,然後它會把程式碼轉換成跨 browser 的 JavaScript 碼。

在快速看過 GWT UI frame,我發現這遠比預期的好上很多。不過很可惜的是,我發現目前 GWT 2.0 版在開發 HTML5 的遊戲方面,並沒辦法運作的很好。問題在於 development mode 中,GWT 並沒有實際將程式碼轉成 JavaScript。它會以 Java 的方式執行,然後使用一個中介層來轉換成 JavaScript ojbect 或是 browser 的 DOM,並將結果轉換回 Java 裡。起初我以為這沒啥影響,因為只有在呼叫一卡車 DOM API 或是因為某些原因要儲存一堆東西在 JavaScript object 當中,這才會是個問題。偏偏在我的 HTML5 遊戲當中就都遇到了 Orz。我在 HTML5 的 canvas 上畫了一堆東西,需要呼叫一堆 API。我也使用 JSON 來儲存遊戲資料,也就是說要用 JavaScript object 的形式存一堆東西。

結果就是,在 development mode 當中,我的遊戲執行速度慢到爆炸。這在 Chrome 上特別嚴重,因為 sandbox 的設計讓 Chrome 的 GWT development plugin 在 browser 跟 Java code 之間轉換特別慢。如果換到 Firefox 上頭就還能忍受,不過我發現我最佳化的方向有誤。看起來似乎在 development mode 才會跑的很慢(像是用 JSON 作遊戲存檔超慢讓 browser 噴了 timeout),如果實際 compile 成 JavaScript 就會很跑的很快。將 Java 碼轉換到 browser JavaScript 引擎上運作的負擔,讓開發人員很難確定遊戲實際跑起來會是啥樣子。

我明白為甚麼 GWT 會設計成這樣。JavaScript debugger API 可以讓 GWT 這類的工具將 JavaScript 碼的行數跟變數與程式設計師寫的 Java 碼作對應,但是大多數的 browser 並沒有公開這類 API。好在像 Firefox 這類的 browser 變得越來越成熟而有這類的 API,所以我期待未來有人可以重寫 GWT,可以在 development mode 時實際轉換 Java 碼變成 JavaScript,而且還是可以作 debug。

在這段期間,我會用 GWT 完成我的遊戲,然後回頭寫純 JavaScript 遊戲。我發現 JavaScript 的自由格式、非結構化性質是沒啥生產力的,但我也疑惑 Java 的 static type 會是解決這問題的最好方法嗎?當我在寫 Smalltalk 時,所有東西都可以是 dynamic type,但寫起來還是相當有效率。Smalltalk 把程式碼用很結構化的方式組織起來,所以讀程式碼或是在裡頭找東西都很容易。到目前為止,我寫的 JavaScript 程式碼風格太自由了,導致即使搭配 JDST 的 Eclipse 也無法分析的很好,只能用簡單的方法瀏覽程式碼。也許我該嘗試用比較結構的方法來寫程式,然後找看看是否有合適的編輯器可以建立出有用的架構,從而讓我讀寫 JavaScript 碼時更有效率。

2010-12-25

GWT 2.1.1 現身

今年十月的時候,我們推出 GWT 2.1,當中提供了一個 framework 與一套工具,使建立商業網站的 application 變得很容易。從那之後,我們就從 GWT 社群那兒得到一些很棒的意見回饋,所以今天我們很高興地宣佈:這些意見已經整合到最新的版本 GWT 2.1.1 啦。詳細的內容如後所述,不過如果你有興趣的話,也可以從這裡開始。

對於 Eclipse 的使用者,只要點螢幕左下角的「Google updates available」的圖示,或是選擇「Help」→「Check for update」。

我們還更新了 Google Plugin for Eclipse 與 GWT Designer 。這兩個都可以透過下面的連結來安裝:
每個產品的主要更新如下:

GWT SDK
GWT 2.1 推出的 RequestFactory 廣受 GWT 開發團隊以及 GWT open source 社群的關注。基於這些回饋意見,我們增加了這些東東:
  • 支援 non-static service object 的 service layer。
  • 支援 value object。
  • 一個 request 可以包含多個 method 呼叫。

Google Plugin for Eclipse
  • SpringSource Tool Suite (STS) 來加強 UiBinder 的錯誤回報機制。
  • 透過移除沒用到的 Java builder 以及利用最新版 STS 當中的 AspectJ 修正來優化 IDE 的使用經驗。
  • 更新 Speed Tracer,在啟動前就完成完整的 J2EE publish。

GWT Designer
我們很樂意聽到你所提出的問題或是意見回饋,可以的話就到 Google Web Toolkit Group 暢所欲言吧!

2010-12-12

App Engine 團隊快樂放假啦——SDK 1.4.0 釋出

原文網址:http://googleappengine.blogspot.com/2010/12/happy-holidays-from-app-engine-team-140.html

技術校正、審閱:tkcn

App Engine 1.4.0 版出來啦!這是我們本年度最重要的一個版本,裡頭包含一堆開發人員已經等很久的超棒特性:
  • Channel API — 一個雙向的 channel,無須 polling 就可以直接 pushing notification 到在 client 端運行的 JavaScript,使 server 能直接跟使用者的 browser 溝通。這個 service 使用與 Google Talk 一樣的技術,可以很輕鬆地建立即時的 application,例如多人遊戲、聊天室、或是任何協同作業為主的 application。
  • Always On—對於高優先等級、但是低流量或不定流量的 application,現在可以用 App Engine 的 Always On 功能來保留 instance。Always On 是一個進階版的功能,每個月需另外付 $9。它可以為你的 application 保留三個 instance,即使沒有任何流量也絕對不會關掉。這減緩了小流量或是變動流量的 application 在 loading request 的影響。
    App Engine 管理控制台中的 Instance 頁面,當中 Always On 是開啟的。
  • Warm Up Request—這個功能會預測是否需要更多的 instance,並且在使用者的流量傳入之前就先載入新的 instance。如此一來,就可以減少處理 request 的時間。透過 app.yaml 或是 appengine-web.xml 可以對所有 application 啟用這個功能,當 application 已經購買 Always On 也會預設開啟。一旦啟用這個功能,當 application 開始處理使用者的傳輸量前所可能要載入的新 instance 時,warm up request 就會送出。

同時,我們花了很多時間在減少或移除現存 App Engine API 的限制:

  • 拿掉背景工作的 30 秒限制—在這個版本中,我們大幅提高來自 Task Queue 跟 Cron 發出離線 request 的時間限制。現在可以執行長達 10 分鐘的程式而不會中斷。
  • 加大 API call 的大小限制—新的 API 架構讓我們可以開始取消許多 App Engine API 的 1MB 大小限制。這次已更改下列 API:
    • URLFetch 的 response 大小限制從 1MB 提升到 32MB。
    • 批次存取 Memcache 的次數也提高到 32MB。
    • Image API 的 request、response 大小限制從 1MB 提升到 32MB。

Mail API 所送出的附加檔案大小限制從 1MB 提昇到 10MB。
你可以想像,這當中的某些改變大幅度地擴展 application 的範圍,並輕鬆地用 App Engine 來完成。所以,趁熱下載 SDK 吧!

2010-08-26

對戰吧~GWT 踩地雷! [下]

跟電腦(web server)對戰的故事大概是這樣的:
  1. 跟 server 要求開始一場遊戲
  2. 跟 server 取得遊戲資訊
  3. 根據遊戲資訊繪製畫面
  4. 將玩家踩的地點傳送給 server
    1. 命中地雷→更新遊戲資訊
    2. 沒有命中→輪到 AI 踩地雷→更新遊戲資訊
  5. 檢查是否有某方獲勝?
    1. 有→結束。
    2. 沒有→回到步驟 3
粗體的部份是跟 server 有關的部份。
那麼,以 RPC、或說以程式的講法
server 只需要提供兩個 method
  • startGame():負責開遊戲 
    • 回傳:該場 game 的 uid
  • shoot():傳送玩家踩的地點
    • 參數:uid, x, y
    • 回傳:GameInfo 物件
因為懶得多設計一堆有的沒的
所以開場之後讓 client 端程式自動踩 (-1,-1) 這個非法位置
就會進入到步驟 2~4 的循環當中 [逃]

好了,終於要進入到 GWT RPC 的部份了。
這裡打算跳過理論、架構的部份
直接以程式碼來說明一切...

首先,是要有一個 MineService 的 interface
繼承自 com.google.gwt.user.client.rpc.RemoteService
裡頭就是宣告上頭說得那兩個 method
@RemoteServiceRelativePath("mineRPC")
public interface MineService extends RemoteService {
  public String startGame();
  public GameInfo shoot(String id, int x, int y) throws Exception;
}
至於那個 annotation 先跳過,後頭會解釋

再來是一個對應的 interface,名稱通常是後頭補 Async
public interface MineServiceAsync {
  void startGame(AsyncCallback<String> callback);
  void shoot(String id, int x, int y, AsyncCallback<GameInfo> callback);
}

這兩個 class 必須放在 gwt compiler 會處理的目錄下(例如 client)

有 interface 自然有實做的 class
MineService 對應實做 class 通常叫做 MineServiceImpl
會長成這樣子:
public class MineServiceImpl extends RemoteServiceServlet implements MineService {
  private AI_Interface ai = new RandomAI();  //FIXME change your ai here!
  
  @Override
  public String startGame() {
    String id = UUID.randomUUID().toString();
    MineGM setting = new MineGM();
    setServer(id, setting);
    return id+",Random";
  }
    
  private void setServer(String id, MineGM setting){
    this.getThreadLocalRequest().getSession().setAttribute(id+"ID", setting);
  }
  
  private MineGM getServer(String id) throws Exception{
    String name = id+"ID";
    if(this.getThreadLocalRequest().getSession().getAttribute(name)!=null){
      return (MineGM) this.getThreadLocalRequest().getSession().getAttribute(name);
    }else{
      throw new Exception("還沒開局");
    }
  }
  
  @Override
  public GameInfo shoot(String id, int x, int y) throws Exception{
    MineGM server = getServer(id);
    if(x==-1 || y==-1 || server.getMap()[x][y]!=-1){
      return MineGM.toGameInfo(server);
    }
    
    if(!server.shoot(x, y, MineGM.USER)){
      int[] xy = new int[2];
      do{
        ai.guess(MineGM.toGameInfo(server), xy);
      }while(server.shoot(xy[0], xy[1], MineGM.AI));
    }
    
    setServer(id, server);
    return MineGM.toGameInfo(server);
  }
}

這個 class 還會繼承 RemoteServiceServlet
往上追溯,parent 是 HttpServlet
也就是說,MineServiceImpl 也是一個 HttpServlet
雖然 GWT RPC 很神奇地包裝好許多東西
但終究還是 base on JSP
所以寫這個 class 時,就不用管 GWT 的重重限制
只要 web.xml 有設定正確就好

講到 web.xml,回頭講一下 MineService 的 annotation
用 RemoteServiceRelativePath 設定 servlet-mapping 會比較方便
<!-- in web.xml -->
<servlet-mapping>
 <servlet-name>mineRPC</servlet-name>
 <url-pattern>/_mine/mineRPC</url-pattern>
</servlet-mapping>

url-pattern 的值,前半段 _mine 是在 gwt.xml 中
設定 <module rename-to='_mine' >
後半段 mineRPC 就是在 MineService 設定的值
(沒有用這個 annotation,得要多好幾行煩死人的 code)

至於遊戲資訊,我選擇塞在 session 當中
在 RemoteServiceServlet 要取得 session 比較囉唆一點
得要這樣才能取得 session
this.getThreadLocalRequest().getSession()
其餘的程式碼... 嗯... 不在 GWT 的範圍當中,跳過 XD

server 端的程式碼解決了,現在來看 client 端
client 端必須透過 MineServiceAsync 來呼叫 RPC
不過用法有點奇怪...... Orz

首先要先這樣寫,取得一個 MineServiceAsync 的 object
MineServiceAsync msa = GWT.create(MineService.class);
然後就可以用 msa.shoot() 來告訴 server 要踩哪個位置
但是事情還沒完,除了標準的 parameter
得要傳一個為 AsyncCallback 的 parameter
這是讓 server 端處理完畢後可以 callback 的一個手段
初期通常都會用 anonymous class 來解決
所以程式碼會長得像這樣:
msa.shoot(this.gameID, hitX, hitY, new AsyncCallback<GameInfo>(){
      @Override
      public void onFailure(Throwable caught) {
        Window.alert("shoot : "+caught.getLocalizedMessage());
      }

      @Override
      public void onSuccess(GameInfo result) {
        setShootResult(result);
      }
    });

這邊要注意兩件事情

其一,RPC 傳遞/回傳的 class(及其 field)
除了 primitive data type 外
一定得 implements IsSerializable
還必須是 GWT 允許的 class
此外,自訂的 class 還必須讓在 GWT compiler 會處理的目錄下
不然實際跑起來就會有(很難看懂的)錯誤訊息

其二,呼叫完 msa.shoot() 之後
下一步並不會執行 onSuccess()/onFailure()
更正確來說,在寫 client 端程式碼的時候
並不會知道 onSuccess()/onFailure() 什麼時候會呼叫到
端看 server 處理以及網路傳輸的速度... etc
這是 callback 的特性,在踩地雷的 case 當中並不會造成困擾
但在其他實務上,如果發現怎麼 RPC 回傳值都不正確
那大概就是忘記這個性質所導致的...... Orz

喔對... 都忘記講一個大前提了
要用 GWT RPC,server 必須是 JSP container

同時也來講講 GWT RPC 的好處 \囧/
一言以蔽之就是「通通傳便便
client 不用組 query string 或 post 內容
server 端不用準備對應的 url
client/server 都不需要剖析傳遞的資料
甚至可以傳遞(客製化的) exception
在 client 端的 onFailure() 可以分門別類處理......
程式碼看起來、寫起來都很 OO、都很 Java
以一個 Java Programmer 來說,有什麼比這個更快樂的事情呢? XD
(連 xml 都沒有用到呢! [握拳])

好了,「對抗電腦版的踩地雷」拆解到這裡
只剩下地雷區要設定 click 的 handler
收到 GameInfo 之後要更新畫面
以及電腦 AI 設計這些功能
相信你一定可以自己寫的很開心的,就跳過不細談了

如果你想偷懶想拿寫好的程式碼來執行看看,
可以到這裡下載。也歡迎投稿你的 AI 設計

Enjoy GWT and have fun! \囧/