- 追加された行はこの色です。
- 削除された行はこの色です。
- Linda へ行く。
#contents
*Lindaライブラリ入手方法
Linda serverの本体と、通信プログラムの例です。 通信プログラム作成
の参考にして下さい。cvsでは、以下のプロジェクト名でチェックアウトしてください。
Game_project/Linda
*Lindaの解説
**Lindaとは
Lindaとは、タプルと呼ばれるIDとDataがセットになったものを、各クライア
ントがサーバに対して out,in,rd などのコマンドを用いて読み書きすることによって通信を行うシステムです。 サーバーに蓄えられたデータへのアクセスは、 APIにタプルのIDを指定することによって行います。
#ref(linda.jpeg,center)
**サーバ (ldserv.c)
Lindaサーバのソースはldserv.cのみです。~
サーバは、新規にクライアントが接続
すると、タプルスペースのID65535にシーケンスな番号をデータとしてもつタプル
を蓄えます。クライアントがこのID65535からタプルを取得(inを用いる)することで、
クライアント毎にユニークな番号を割り当てることができます。
**クライアントAPI (lindaapi.c,lindaapi.h)
タプル送受信のAPI(out,in,rdなど)を用いると、タプルはCOMMANDキューに溜められていき、プログラマが指定したタイミングで(psx_sync_n()実行時)、 一気にサーバに送信されます。つまりAPIを実行した段階ですぐに通信が 始まるわけではありません。
また、サーバからのタプルの受信も psx_sync_n() を実行したときに行い
ます。受信したタプルは REPLYキュー に蓄えられ、psx_reply() で取り出します。
また、コールバックを用いて受信したときのアクションを登録することができます。
**LindaのAPI
--void start_linda(hostname);
通信を初期化して、Linda APIが使用可能にする
--void psx_sync_n();
COMMANDキューに溜められたコマンドとデータをサーバへ送信します。
また、サーバからデータを取得し、REPLYキューへ溜めます。
--int psx_out(int id, char *data, int size);
dataが示すアドレスから、指定したIDのタプルへsize byteのデータ
を 書き込むコマンド。返り値は、このコマンドのsequence番号です。
--int psx_in(int id);
指定したID番号のタプルから、データを読み込むコマンド。 このコ
マンドを使用した場合、サーバー上に存在するタプルは、 クライア
ントがデータを読み込んだ後削除されます。 返り値として、
sequence番号を取得します。
--int psx_rd(int id);
指定したID番号のタプルから、データを読み込むコマンド。 このコ
マンドを使用した場合、サーバー上に存在するタプルは、 クライア
ントがデータを読み込んだ後も残ります。 返り値として、sequence
番号を取得します。
--int psx_wait_rd(int id);
基本的に psx_rd() と同じですが、サーバの タプルスペースにデー
タがあってもすぐには読みこまず、 次にデータが書き込まれるまで
待ちます。 データが書き込まれたときのみそのデータを読みこみ、
クライアントへ送信します。 返り値として、sequence番号を取得し
ます。
--unsigned char psx_reply(int seq);
REPLYキューからデータを取り出すときに使用します。
psx_in/psx_rdで受け取ったsequence番号を引数とし手渡すと、 要求
したデータがREPLYキューにある場合、 データが格納されている
TUPLEへのポインタを返します。 psx_replyによって返されたポイン
タの先には、 先頭にそのTUPLEのヘッダ情報が 12bytes 格納されて
いて、 その後にデータそのものが格納されています。
--int psx_callback_in(int id,void (*callback)(char *tuple,void *),void *obj);
サーバからデータを取得した際、REPLYキューに溜めずに任意の関数
を 呼び出すよう指定することができます。引き数の callback 関数
ポインタは psx_sync_n() の中で(データが到着したら)呼ばれます。
上記の psx_in / psx_rd / psx_wait_rd のコマンドの各々に対して
psx_callback_in / psx_callback_rd / psx_callback_wait_rd が用
意されています。細かな説明は後の方でします。
--int psx_get_datalength(unsigned char *tuple);
psx_reply()等で得たTUPLEのヘッダ部分からデータの大きさの情報
を得る関数です。他にシーケンス番号、ID、モードの情報を得る関数
も用意しています。 (各々 psx_get_seq(), psx_get_id(),
psx_get_mode())
**マクロ
LindaではTUPLEのヘッダ情報やデータ部分へアクセスするためのオフ
セットと して以下のマクロを用意しています。
#define LINDA_MODE_OFFSET 0
#define LINDA_ID_OFFSET 1
#define LINDA_SEQ_OFFSET 3
#define LINDA_DATA_LENGTH_OFFSET 7
#define LINDA_HEADER_SIZE 12
これらはpsx_reply()で得たTUPLE(unsigned char のポインタ)のデータ部分
やヘッダ部分などを取得するときなどに用いられます。データ取得は プログ
ラミングの流れで示します。 以下はpsx_reply()で得たTUPLE (tuple) のヘッ
ダ部分を表示しているところです。
printf("MODE: %c\n",*(char*)(tuple+LINDA_MODE_OFFSET));
printf("ID: %d\n",*(short*)(tuple+LINDA_ID_OFFSET));
printf("SEQ: %d\n",*(int*)(tuple+LINDA_SEQ_OFFSET));
printf("DATA_LENGTH: %d\n",*(int*)(tuple+LINDA_DATA_LENGTH_OFFSET);
ヘッダ情報は異なるエンディアンのCPU上でも対応するように整数の上位 ビッ
トが配列の前の方にくるように揃えているので、予想した結果がでない かも
しれません。ヘッダ部分の情報を得るにはpsx_getから始まる関数群を 使って
下さい。
*プログラミングの流れ
まずは、プログラム初頭でstart_linda(hostname)を 実行して通信を行えるようにしておきます。
**データを送信する場合
-psx_outでコマンドをキューに追加 (1)
psx_outした時点では、単にキューへためられるだけで、 まだ通信は開始され
ていません。 Lindaでは、char型かunsigned char型でしかデータを送ること
ができません。 ただ、符号の問題を避けるため、char型ではなくunsigned
char型を使いましょう。 少し詳しく言うと、psx_outは、与えられたポインタ
のアドレスから、 1byteずつ順番に指定されたbyte数サーバーへ送信するとい
う事です。 そこで、送りたいデータを格納している変数や構造体等が
unsigned char型以外である場合、unsigned char型に タイプキャストしてや
る必要があります。
-psx_sync_nで実際にコマンドを実行 (2)
この時、今までキューにためこまれたLindaのコマンドが、一気に実行されま
す。 PlayStation 2では、描画に関する演算はEmotion Engineの中で行われ
ますが、実際の描画処理はGraphics Synthesizerで行われます。 つまり、描
画処理が行われている間、Emotion Engineには余裕ができます。 その時間を
使用して、通信を行うようにプログラミングしましょう。 (そのタイミングを
考慮して、psx_sync_nを実行しましょう)
-受信相手のデータ受け取りの確認 (3)
通信を行う際、待ち合わせを行い、 データを受け取るまでゲームが止まって
しまってはいけないので、 Lindaサーバーでは非同期通信を採用しています。
もし送信するデータを確実に相手に受け取って欲しい場合は、 相手から受け
取りのサイン(Ack)を受け取るようにするとよいでしょう。
例えば、送信側が psx_out() したデータを受信側が psx_in() で受け取 るよ
うな通信を何回か繰りかえす場合を考えます。 このとき、通信相手がデータ
を受け取った事を確認してから、 次のデータを送らなければなりません。 何
も考えずにどんどんpsx_outを行うと、タプルにまだデータが残っているので
新たなデータを書き込めず、キューにためこまれていきます。 そして、処理
がどんどん重くなってしまい、ゲーム自体もスピードが落ち、 キューがいっ
ぱいになると、失敗するようになってしまいます。 そこで、その確認を取る
ために、相手がデータを受け取ったという サイン(Ack)を、psx_inやpsx_rdを
使用して受け取ります。 Ackを受け取ってから、次のデータをpsx_outして下
さい。 ただし、プログラムが通信を行う部分でループして、 Ackを得るまで
プログラムが止まるというわけではありません! メインの処理は、Ackを待つ
間も動き続ける書き方をして下さい!! そのために、psx_replyはif文で括ら
れているのです。
-(1)からを繰り返し行う (4)
**データを受信する場合(callbackを使わない)
-psx_in/psx_rd/psx_wait_rdでsequence番号を取得 (1)
sequence番号とは、実行したコマンドに与えられる、 整理券の番号みたいな
ものです。
-psx_replyでデータの到着を監視 (2)
データが来ていれば、それに対する処理を実行する。 来ていなければ、その
ままプログラムの処理を進める。
-データを任意の変数・構造体に展開 (3)
データを任意の変数・構造体に展開する際、 データを送信するために 1byte
ずつに分解されたデータ を、元の形に組み立てる必要があります。
psx_reply() で得たポインタ(仮に reply とします)の 指す先にはTUPLEのヘッ
ダが含まれているので、 実データ(RealData型とします)は
(RealData *)(reply + LINDA_HEADER_SIZE)
でアクセスできます。LINDA_HEADER_SIZE は 12 に置き換えられます。
memcpyを使用すると便利です。
-psx_replyで受け取ったポインタをfreeする (4)
通信で受け取ったデータは、psx_sync_n()の実行時にmallocされ、 メモリの
何処かへ蓄えられています。 それを放っておくと、データを受け取る度に、
そのデータ分だけ メモリを消費し続ける事になり、メモリ不足の原因となり
ます。 よってデータを任意の変数・構造体に展開し終えた後に、 psx_reply
で得たポインタをfreeして下さい。 勿論、データを任意の変数・構造体に格
納せずに、 psx_reply で得たポインタをそのまま使用しても構いません。
-送信相手へAckを送る (5)
必要ならば データを受け取ったというサインを送信者へ送ります。 送信者が
Ack だと分かるものなら、送るデータは何でも構いません。
- (1)からを繰り返し行う (6)
**データを受信する場合(callbackを使う)
-psx_callback_in / psx_callback_rd / psx_callback_wait_rd を使う (1)
callbackを使う場合は引き数にcallbackする関数と、 その関数の引き数にす
る構造体を指定します。この callbackする関数の型は
void function(char * tuple, void * obj);
となっています。objは任意の構造体のポインタです。 この関数の内部の処理
は自由に作ってもいいのですが、 REPLYキューに蓄えられないので、
psx_reply()を実行しても 答えは返ってきません。 引き数の tuple は
psx_sync_n() 内で malloc されているので、 いらなくなったら free() して
ください。
データを受け取ったという情報をAckとして 送る場合などは、この
function() 内で送信する必要があります。
この callback 関数を上手く書くと、psx_reply() がいらない ようなプログ
ラムも可能です。
*Linda's Tips
Lindaでのプログラミングの際、以下のことに留意しておいて下さい。
-通信プログラムの例題が、Game_project/Linda/example にあります。
-IDの幅は、0〜65534です。
-65535のIDでpsx_inすると、Lindaサーバーから個別のIDを得られます。
-IDは、1から順に与えられます。また、IDはASCII文字で送られて来るので、 atoi 関数で数値に変換して下さい。
-一度に送れるデータは、1つのタプルにつき、2^32-12bytes(intの最大値-ヘッダサイズ)です。
-NULLは送らないで下さい。
-コマンドをためすぎてキューがいっぱいになると失敗します。