言語ゲーム

とあるエンジニアが嘘ばかり書く日記

Twitter: @propella

Croquet2D の非同期処理

future メッセージとは

Croquet2D では、様々なところで future メッセージが使われます。http://d.hatena.ne.jp/propella/20060521/p1 future メッセージはマルチタスクを実現するための仕組みの一つで、以下の特徴があります。

  • 一つの世界(アイランド)で同時に動く future メッセージは一つだけなので、排他制御に頭を悩ます必要が無い(ノンプリエンティブ)。
  • セマフォを wait すると、他の future メッセージに自動的に処理が移る。という事はネットワーク処理等で待たされる時も全体が固まらないという事。
  • 何秒後にメッセージを実行するか設定出来る(レシーバが普通のオブジェクトの時のみ)。
  • イベント制御の実装に使われている(後述)。

注意するべき点としては、状況によって意味が微妙に違う事です。

  • レシーバが FarRef の時。メッセージはすべての接続されているアイランド全部に送られる。
  • レシーバが普通のオブジェクトの時。メッセージは一つのアイランドだけで実行される。
  • メッセージの返り値が不要な時。処理は戻らない。
  • メッセージの返り値が必要な時。プロミスが返される。処理が終わるとプロミスから返り値を取得できる。しかし阿呆な事に、デバッガでどこに返るのか追うことが出来ない。

イベントとは

古くから Smalltalk にはオブザーバーパターンの実装として、changed: update による更新通知モデルが備わっています。Croquet2D (Tweak) では、新しいイベントモデルとして、スクリプトイベントと言う仕組みがあります。http://d.hatena.ne.jp/propella/20050209/p1

objectA := CObject new.
objectA name: 'objectA'.
objectB := CObject new.
objectB name: 'objectB'.

objectB startScript: [objectB name inspect] when: {objectA. #wakeUp} "イベントの設定".

objectA signal: #wakeUp "イベントの起動".

startScript: のブロックは future メッセージとして実行されます。スクリプトイベントは Croquet2D (Tweak) の基本的な部分で大量に使われていますが、直感的ではない動作がありますので注意が必要です。それは、一つのイベントが終わるまで同じイベントが実行されないという事です。この動作を理解する為には、イベントの低レベル実装を理解する必要があります。

イベント処理の書き方

あとで書く

イベントの実装

基本オブジェクトである CObject にはいくつか独特のインスタンス変数がありますが重要なのは myEvents です。

  • myEvents : このオブジェクトがどのイベントに反応するかを表す辞書。

startScript:when: 等によってイベントが設定されると、myEvents に記録されます。キーはイベントの種類を表すシンボル。値は反応するイベントの集合です。上の例では objectA の myEvents に an IdentityDictionary(#wakeUp->#(TFutureEventMessage(#evaluate: -> objectB)) ) が入ります。TFutureEventMessage はイベントを生成する時に作られるオブジェクトです。TFutureEventMessage 以外にも生成の仕方によって TTickMessage や様々なイベントオブジェクトがあります。

signal: が実行されると、オブジェクトは myEvents を検索し、対応するイベントオブジェクトを起動しようとします。この際の動作はイベントオブジェクトによって異なりますが、代表的な TFutureEventMessage の場合、まずイベントは eventBuffer (TEventBuffer) に格納されます。イベント実行時(TFutureEventMessage>>value) eventBuffer の最初のものが使われ、残りは破棄されます。もしも古いイベント情報が必要な場合は withDroppedEventsDo: を利用できるみたいです。