@node ImplementVObjectClass, Variable Index, Concept, Top
@comment  node-name,  next,  previous,  up
@chapter VObjectクラス群の実装について

VObjectクラスは機種非依存で，VObjectで必要とされる基本的な機能のみを提供します。
ツールキットなどに依存した各部品ごとの生成や属性の設定といったコードは，各サブクラスで実装します。
この章では，VObjectクラス群の各種動作の詳細と，サブクラスのインプリメントに必要な作業となることを説明します。

@menu
* CreateObject::                オブジェクトの生成                
* DestroyObject::               オブジェクトの解放              
* changeStatus::                ステータスの変更               
* Delivery::                    OSからインスタンスへのイベントの受け渡し                   
* manageTree_I::                ツリーの管理                 
* AutoArrange::                 部品の自動配置                
* lockProcess::                 マルチスレッド対応のためのロック処理について               
@end menu

@node CreateObject, DestroyObject, ImplementVObjectClass, ImplementVObjectClass
@comment  node-name,  next,  previous,  up
@section オブジェクトの生成

オブジェクトの作成では，まずそのクラスのstaticメソッド，create()が呼び出されます。
create()では一般的に，まず親をロックし，指定された親に対してcheck_parent()メソッドを呼び出し，自分が子供になれるかどうかを確認します。子供になれる場合は，そのクラスをnewを呼び出します。すると基底クラスVObjectのコンストラクタは，stsの値の初期化やセマフォの初期化などを行います。サブクラスのコンストラクタは通常何もしません（宣言もされません）。
こうしてインスタンスにメモリが割り当てられたら，create()はそのインスタンスに対してVObject::create_fin()メソッドを呼び出します。これは，まずサブクラスのvirtualメソッドcreate_do()を呼び出して，OS/ツールキットのGUI部品を作成し，その情報をメンバ変数infoに保存させます。次に，set_parent()によってオブジェクト同士の親子ツリーが正しく構成されるようにポインタを張り，最初にcreate()に指定されなかったstsをデフォルト値にset_statusします。
以上の処理が終わると，create()は親オブジェクトのロックを解除します。これでオブジェクトの作成が完了し，そのインスタンスへのポインタがcreate()の返値となります。

やや複雑な関数呼び出しが行われますが，どのクラスにも共通な処理はほとんどVObjectに集約されているので，これによって新たなサブクラスの実装が比較的簡単になります。

サブクラスを実装するに当たっては，オブジェクトの生成に関して，以下のメソッドのオーバーライドが必要になります。

@enumerate
@item
create_doをオーバーライドし，そこでGUI部品を作ってinfoにそのポインタを張る，という部分を実装する。

@item
子オブジェクトの種類や数を制限することができます。max_children()というstaticクラスメソッドで数を，check_parent_exというstaticクラスメソッドで種類などを制限できます。特にオーバーライドしなければ，スーパークラスのものが使われます。（VObjectではそれぞれ，子は持てない，種類の制限なし，となっています。）

@item
サブクラスのcreate()メソッドも実装が必要ですが，これはほとんどの場合，VObject.hに用意されたクラス間数テンプレートcreate_tmp<VObjectSubClass>を使えばいいでしょう(VObjectSubClassにはそのサブクラス名が入ります)。VWindowの実装では，親をdisplayにしなければならないという制約のため，テンプレートを呼び出す前に少し処理が入ります。
@end enumerate

@node DestroyObject, changeStatus, CreateObject, ImplementVObjectClass
@comment  node-name,  next,  previous,  up
@section オブジェクトの解放

VObject::destroy()がコールされると，まずvirtualメソッドdestroy_do()を呼び出します。
ここで，サブクラスの種類に応じた，OS / ツールキットのGUI部品を削除・解放を行います。
その後，VObject::destroy()は，detach()を呼んで子供を全てdestroyするとともに親オブジェクトとの関係を切り離し，v_zonbie_insert()によってインスタンスをゾンビリストに登録します。

オブジェクト生成時に，メンバ変数destroy_timeはV_OBJECT_ALIVE = 0x7fffffffにセットされています 。v_zonbie_insertは，これに現在時刻をセットします。

static void VObject::v_zonbie_tick()は，tick_threadから秒数V_OBJECT_ZONBIE_INTERVALごとに実行され，ここでv_zonbie_listに入っているものを順に調べ，削除後V_OBJECT_ZONBIE_DELAY経過したものについてオブジェクトを解放します。

サブクラスの実装に当たって，解放処理に関して必要なことは一つだけです。

@enumerate

@item
destroy_doをオーバーライドし，オブジェクトが管理していたGUI部品を削除するようにする
このとき，必要ならVObject::detach()をコールすると，子が全てdestroyされ，また親オブジェクトの子リストから登録が取り除かれます。detachメソッドは２回以上呼んでも何もしないため，安全です。
@end enumerate

@node changeStatus, Delivery, DestroyObject, ImplementVObjectClass
@comment  node-name,  next,  previous,  up
@section ステータスの変更

set_status/get_statusは，createやdestroyとは異なって，それ自体virtualメソッドであり，直接オーバーライドします。しかし，VObjectクラスに実装された機能をサブクラスから有効に利用することができます。

@enumerate
@item
基本的に，set_statusメソッドは，オブジェクトのステータスを格納するためのメンバ変数VObjectStatus stsを変更するとともに，OSやツールキットのメソッドを呼び出して値を変更する，という内容になります。

VObject::set_status()に，stsの内容を変更する処理は実装されています。サブクラスのset_status()の内部でVObject::set_stauts(s,flags)を呼び出せば，stsの内容の変更が行われます。その後，フラグに応じて，ステータスをOSなどのGUI部品に反映します。VButtonのように別な基底クラスでset_stautsをオーバーライドしている場合は，そちらのset_statusを呼び出します。このようにすることで，set_statusの機能を「継承」することができます。

なお，desceiptorのように，ポインタでデータを渡された場合，set_statusでは新たにメモリを確保してその内容をコピーして使用する必要があります。場合によっては，VObject内ではなく，OS / ツールキット内に保存しても構いません。

子のステータスが変更され，それを親オブジェクトに通知する必要がある場合，親オブジェクトのchild_status_changedメソッドを呼び出します。この呼び出しは，子のlockの外で行って下さい。さもないとデッドロックを引き起こす原因になります。

@item
get_statusメソッドは，フラグで指定されたVObjectStatus stsの値をそのまま返すか，必要に応じてサブクラスに依存した値をOS/ツールキットから取得して返します。

ツールキットに依存した値を返す場合は，サブクラスでオーバーライドしたget_statusメソッドで値をセットし，該当するフラグを解除してVObject::get_statusを呼び出すという形になるでしょう。

なお，descriptorのようなポインタを渡す際には，内部のバッファへのポインタを直接返すか，set_buffer()のように後で自動的に解放されるような一時メモリへのポインタを渡すようにします。l_stringやn_stringの返値は後者の一例です。従ってget_statusで取得したポインタのデータを解放したり変更したりしてはいけません。

@end enumerate

@node Delivery, manageTree_I, changeStatus, ImplementVObjectClass
@comment  node-name,  next,  previous,  up
@section OSからインスタンスへのイベントの受け渡し

ユーザによってボタンがクリックされたり，値が入力されたりした場合，OSからコールバックやイベントハンドラなどを利用してその通知を受け取り，そのオブジェクトのvalue_changed()やdescriptor_changed()を呼び出すようにします。それらのメソッドでは，OS/ツールキットから取得した値をsts.valueやsts.descriptorに格納した上で，VObjectStatusに設定されていたイベントハンドラを呼び出すようにします。

@node manageTree_I, AutoArrange, Delivery, ImplementVObjectClass
@comment  node-name,  next,  previous,  up
@section ツリーの管理

ツリーの変更は，VObject::createでオブジェクトが生成されるとき，及びオブジェクトの削除時にVObject::detachで行われます。親子の両方を操作する場合，必ず親オブジェクトをlockしてから子をlockする，という実行順を守ってください。
通常，create_do, destroy_doをオーバーライドして生成する場合は，特にロック処理を意識しなくても，それらの呼び出し元であるVObjectクラスのメソッドが親から順にロックしています。

@node AutoArrange, lockProcess, manageTree_I, ImplementVObjectClass
@comment  node-name,  next,  previous,  up
@section 部品の自動配置

GTKではオブジェクトの階層構造とパッキングの概念に基づいて自動的に部品を配置する位置が決められますが，MacのPowerPlantなどにはそのような機能はありません。

したがって，GTKのような自動配置を実現するために，子オブジェクトの座標計算をするライブラリを用意しました。それがVLayoutクラスです。

クラスメソッドの内容は，別ドキュメントのリファレンスを参照して下さい。

VLayoutクラスは，指定されたVObjectオブジェクトクラスがとるべきサイズ，その子オブジェクトのサイズと位置を計算して返す，VObjectとは独立したクラスです。主にウィンドウやボックスなど，子オブジェクトを格納することのできるVObjectから呼び出されて，配置に必要な情報を返す機能をもちます。

新しいオブジェクトが生成されたり，ラベルなどが変更されて部品の大きさを変更する必要が生じたら，staticメソッドVLayout::mark(VObject* obj)を呼び出します。すると，「そのオブジェクトが属するウィンドウ」にマークがつけられます。このメソッドは，オブジェクトのロックの外側で呼び出して下さい。さもないとデッドロックを引き起こす原因になります。

いろいろな処理の後，イベントループなどからstaticメソッドVLayout::layout_marked_window()が呼び出されると，マークの付けられたウィンドウに対して，

@example
set_stauts(*, VSF_CALC_MIN);
set_status(*, VSF_LAYOUT);
@end example

が発行されます。（*はダミーの変数です。）

set_stauts(*, VSF_CALC_MIN)が呼び出されたオブジェクトは，自分の子にもそれを伝えていきます。こうしてウィンドウ内の全てのオブジェクトにこのメッセージが通知されます。このメッセージが通知された末端のオブジェクトは，自分の必要とする最小のサイズを計算し，自分のsts.min_sizeにセットします。親は，子で処理が終わった後でVLayoutの用意したメソッドを呼び出し，子オブジェクトを全て格納するのに必要な最小サイズを計算し，やはりsts.min_sizeにセットします。

こうして，min_sizeがウィンドウ内の全てのオブジェクトにセットされると，set_status(*, VSF_LAYOUT)メッセージの処理が行われます。
このメッセージを受信すると，またVLayoutクラスの用意しているメソッドを呼び出します。すると，現在のサイズにおける子オブジェクトのサイズや位置などが計算され，このメソッドからそのオブジェクトのset_statusを呼んでセットされます。
さらにオブジェクトは子にもVSF_LAYOUTメッセージを送信します。

これによってウィンドウ内の全てのオブジェクトが再配置されます。

サブクラスの実装に当たっては，普通，set_statusの中で，

@enumerate

@item
オブジェクトのロック中に，

@example
if ( flags & VSF_CALC_MIN ) @{
    for ( VObjectList *list = sts.children ; list ; list = list->next )
        list->object->set_status(0, VSF_CALC_MIN);
    VLayout layout;
    layout.layout_in_frame(this, true);
    sts.min_size = layout.parent_min_size();
@}
if ( flags & VSF_LAYOUT ) @{
    VLayout layout;
    layout.layout_in_frame(this);
    layout.do_layout(this, sts.children);
@}
@end example


@item
ロックの外で

@example
if ( flags & ( VSF_HOMOGEN | VSF_SPACING | VSF_ALIGN | VSF_PADDING ) ) @{
    VLayout::mark(this);
@}
@end example

のようなコードを入れることで自動レイアウト機能を実現できるでしょう。
layout_in_frameの代わりに，サブクラスに応じた並べ方を実現するためのメソッドを配置することになるでしょう。
@end enumerate

@node lockProcess,  , AutoArrange, ImplementVObjectClass
@comment  node-name,  next,  previous,  up
@section マルチスレッド対応のためのロック処理について

VObject::lock(), VObject::unlock()というメソッドを用意しました。これは，VObjectのインスタンスごとにセマフォ（VSemaphoreクラスのインスタンス）を持っており，それをロック／アンロックします。同じスレッドから複数回 lock() をコールしても，それと同じ回数だけunlockをコールすれば処理が止まってしまわないようになっています。
各メソッドのpublicなアクセッサの最初にlockを呼び，最後にunlockを呼ぶようにします。

これのラッパーとして，各オブジェクトのpublicメソッドの前後では，V_OP_START / V_OP_ENDというマクロを入れることにしました。
これらは，各アーキテクチャ用ヘッダarch/*/machine/h/v_m.hにあり，必要に応じて処理を追加できますが，最低限，次の動作を行います。

@itemize
@item
V_OP_STARTはセマフォをlockし，オブジェクトがdestroyされていないか調べ，されていたらunlock後V_ER_DESTROYEDをreturnする。

@item
V_OP_ENDはunlockを行う
@end itemize

redrawなど，voidなクラスメソッドでロックが必要な場合は，
_V_OP_START_VOID
を使用してください。


OS / ツールキットがスレッドセーフでない場合，そのような呼び出しをシリアライズするために，msequenceを経由して呼び出しを行います。しかし，メインスレッドでイベントループが回っていて，そこからOS / ツールキットが呼び出されるようなフレームワークでは，イベントループとmsequenceとの間を調停する必要があります。イベントループをmsequenceの中で実行して他の関数の実行中はループを抜けてしまう（GTK+版ではこのアプローチをとっています）か，msequenceからイベントループに何らかの形で実行したい処理を渡して委譲する必要があるでしょう。特に，常にメインスレッドからOS / ツールキットを呼び出さないと行けないような場合には後者の方法になるでしょう。


@sp 2

※GTK+版について

GTK+にはスレッドセーフモードがありますが，これはどうもいろいろ怪しいようなので，使わないことにしました。その代わり，全ての関数呼び出しをmsequenceのms_do経由で行います。
したがってmsequence内でオブジェクトをロックしようとするとデッドロックしてしまいます。そこで，lockがかかっているオブジェクトがある間はgtk_main（これはmsequence内で実行されています）を抜けることによって，gtk_main内からコールバックされた関数からVObjectのpublicメソッドを呼んでもデッドロックしないようになっています。これらは，lock / unlock時に呼ばれる，V_PRE_LOCK/V_POST_LOCKというマクロで実現しています(arch/unix/h/machine/v_m.h)。

また，GTK+, GDK, glibの豊富な関数をms_do対応にするためのグルー関数を次々作っていくのは大変なので，引数N個の関数をms_do経由で呼び出すテンプレート関数_ms_via_N<返値型,引数型1，引数型2, ...>(func, arg1, arg2, ...)を作り，この作業を自動でやってもらうことにしました。いまのところ，引数が6個までの関数のみ呼び出せます。

例えば，short func(GtkWidget *, int) というプロトタイプ宣言を持つ関数を呼び出して結果を受け取るには，

@example
short result = _ms_via_2(func, widget, a);
@end example

とします。キャスト変換が必要な引数などだと，型の同定に失敗することがあるので，その場合は

@example
short result = _ms_via_2<int, GtkWidget*, int>(func, widget, a);
@end example

とする必要があります。

なお，返値型がvoidのときは，テンプレート引数の最初にvoidと書くか，第1テンプレート引数を省略（つまり引数の数を一つ減らす）します。例えば

@example
_via_ms_1<void*>(g_object_unref, sts.info);
@end example

とします。

gccでは，コンパイル時に型がうまく同定できなかった場合，エラーに関数の引数型が表示されますので，それを<>内にコピー＆ペーストすればよいでしょう。

ただ，この_via_ms_Nの実装は

@itemize
@item
関数のプロトタイプを引数6個にキャスト変換

@item
void型関数はint型にキャスト変換
@end itemize

してmsequenceに登録するということをやっているので，一部システムコールは呼び出せないでしょうし，アーキテクチャ・コンパイラによってはうまく動作しない可能性があります。

@sp 2

※Mac Carbon / PowerPlant版について

OS／ツールキットの関数を呼び出す際に実はあまりロックはいりません。むしろセマフォにロックをかけない限りスレッドが切り替わらないので，ない方がいいくらいのものです。したがって，特にmsequence経由で呼び出したりはしていません。VObjectクラスのメンバ変数操作には，GTK同様，VObject::lock/unlockによるロックをかけています。そもそもmainスレッドの中で他のスレッドが呼び出されており，シリアライズされていると考えて良いので，GTKのようにmainを抜けるといったことも必要ありません。
