第5回 メッセージ
前回までの解説で、いろいろな形状の物体をメッセージを使うと、例えば 10 秒間前進すンス、またはすべての Receiver1 のインスタアニメーションできるようになりました。今回るという動きをRUNメソッドで繰り返しているンスにメッセージを送るものです。Receiver1

は、物体が「勝手に」アニメーションするのを場合でも、キー入力された瞬間に 90 度向きをのインスタンスを区別するには、インスタンス見ているだけではなく、今回は物体を「リアル変えることができます。それには、キー入力を番号を RQ レジスタ変数に設定する必要がありタイムに操作」する方法を見ていきましょう。

監視しているインスタンスが、特定のキーが押（リます。そこで、newを実行したインスタンスされたことを検出した場合に「特定の」インス

スト 1 では main ）のレジスタ変数は、生成さタンスにメッセージを送るようにします。そのれるインスタンスに引き継がれることを利用しインスタンスは、メッセージに対応するためのます。

■メッセージの送受信
J3C では、クラスは独立して動作し、他のク動作をEVENTメソッドに記述しておきます。メッ

リスト 1 では、 RX レジスタ変数の値を毎回ラスからメソッドを呼び出したり、データメンセージを受け取ると、どのような動作をしてい変えて n e w を実行しています。生成されたバ（クラスの変数）を操作することはできませても優先的に EVENT メソッドでプログラムさReceiver1 の各インスタンスは、INIT メソッん。クラス（のインスタンス）間で情報を受けれている動作をします。

ドで RX レジスタを RQ レジスタに代入してイン渡すには「メッセージ」を使います。メッセー

メッセージを送るには、Send(インスタンススタンス番号を設定しているため、3つのインジは32ビットの整数です。このメッセージを特番号 , メッセージ)の形で Send 組込み関数をスタンスが区別できるようになります。次のよ定のインスタンスに送ると、受信したインスタ実行します。「インスタンス番号」とは、インうにして実行すると、自分のインスタンス番号ンスのEVENTメソッドが呼び出されます（図1）。

スタンスの RQ レジスタ変数に設定した数値のに続いて受信したメッセージの内容が表示されこれまでのサンプルでは、mainクラスのRUNことで、インスタンス自身が自由に設定するこます。

メソッドで「ESCキーが押されるのを待って終了」という動作をしていました。この場合は単とができます。

new組み込み関数で生成されたインスタンス

j3cc -r event1.j3cに「プログラムの終了」という動作のため、mainは、インスタンス番号（J3Wのマニュアルでは

リスト 1 には、この連載で初めて空でないクラスが「キー入力を監視して押されるのを待「プロセス ID」と呼んでいる）が 0 に初期化さEVENTメソッドが登場しています。EVENTメソッつ」方法で十分でした。これを「ポーリング」れるため、そのままでは生成されたインスタンドは、メッセージを受信した場合にだけ、自動と呼びます。しかし、動作中の物体（インスタスを区別できません。同一のクラスから生成さ的に実行されるメソッドです。RL レジスタ変ンス）がキー入力に反応する場合でも、物体のれた複数のインスタンスに、別々にメッセージ数にメッセージの内容が設定されます。 R e -回転や移動という仕事とキー入力の監視を同時を送る方法を見ていきましょう。

ceiver1 クラスのEVENT メソッドは、RL が1のに処理しなければならないのでしょうか？ 物

リスト1は、main クラスが3つのReceiver1体の運動に関する部分だけに専念して、キー入クラスのインスタンスを生成し、キー入力されす。この例のように EVENT メソッドは、メッ力で動作の変更があったときにだけ、特別に決た値によって、特定の Receiver1 のインスタセージの種類別に異なる処理ができるように場合とその他の場合で異なる文字列を表示しまめられた動作ができると便利です。
この「特別な場合」に実行する命令を処理するために、J 3 C には「EVENT メソッド」が用意されています。EVENT メソッドの役割は「割り込み処理」です。割り込み処理は、他のインスタンス（物体）から送られるメッセージによって起動します。割り込み処理を起動する「割り込み」がメッセージです。これは UNIX のシグナルとシグナルハンドラの関係と同じです。

86

J3W for Linux 入門講座

【図 1】クラス間で情報を受け渡す「メッセージ」【図 2】共有メモリ

switch 文を使うのが普通です。

セージを送ることができます。

Send( インスタンス番号 , メッセージ ) の形式には例外があります。リスト 1 の図2に示すように、すべてのインスタンスからアクセスできるメモリ領域です。共有メモリは■共有メモリ

3 2 ビットの整数の配列で、読み込み時にn=GetCommon( アドレス) とすると、変数 n にSend(0x7FFF, 10)のように、インスタンス番号に「0x7FFF 」（10 進数なら 32767）を指定

リスト2は、インスタンス番号を設定するた共有メモリの値が入ります。書き込み時には、すると、「すべて」のインスタンスに同じメッめに共有メモリを使った例です。共有メモリは、SetCommon(値, アドレス)として使いますが、【リスト 1】event1.j3c// 受信したメッセージを表示するクラス
class Receiver1 {int INIT() {RQ = RX;// インスタンスの識別番号を設定}
int RUN() {Wait();// メッセージ受信を待つ}
int EVENT() {switch(RL) {// メッセージ受信時の処理
case 1 :Char('[');
Number(RQ); // 識別番号を表示
String("] recieve 1"); Char(13);break;
default :Char('[');
Number(RQ);
String("] recieve Unknown Message :");
Number(RL); // 受信したメッセージを表示
Char(13);}}}【リスト 2】event2.j3c// 受信したメッセージを表示するクラス
class Receiver2 {volatile int i;
int SetID() {i = GetCommon(0); // 共有メモリ 0 番地の値を取得
i = i + 1;
RQ = i;// インスタンス識別番号を設定
SetCommon(i, 0);// 共有メモリ 0 番地の値を更新}
int INIT() {SetID();}
int RUN() {Wait();// メッセージ受信を待つ}
int EVENT() {switch(RL) {// メッセージ受信時の処理
case 1 :Char('[');
Number(RQ); // 識別番号を表示
String("] recieve 1"); Char(13);break;
default :Char('[');
Number(RQ);
String("] recieve Unknown Message :");
Number(RL); // 受信したメッセージを表示
Char(13);case 1 : break;}}// キー入力されたら他のインスタンスにメッセージを送信
class main {volatile int key;
int INIT() {RX = 1;// レジスタ変数経由で識別番号を指定
new(Receiver1, 50); // Receiver のインスタンス生成（1）RX = 2;new(Receiver1, 50); // Receiver のインスタンス生成（2）RX = 3;new(Receiver1, 50); // Receiver のインスタンス生成（3）}int RUN() {key = InKey();// キーコード入力
ample.tar.gzwitch (key) {case 0x1B : Stop();// ESC キーで終了
break;
case 'A' : Send(1, 1); // 1 番のインスタンスに 1 を送信
break;
case 'B' : Send(2, 1); // 2 番のインスタンスに 1 を送信
break;
case 'C' : Send(3, 1); // 3 番のインスタンスに 1 を送信
break;
case '1' : Send(0x7FFF, 10); // すべてのインスタンス
break;// に 10 を送信
case '2' : Send(0x7FFF, 20); // すべてのインスタンス
break;// に 20 を送信}}
int EVENT() {}}// キー入力されたら他のインスタンスにメッセージを送信
class main {final int N = 20;// インスタンスの数
volatile int i, key;
int INIT() {SetCommon(0, 0);// 共有メモリ 0 番地に 0 を設定
for (i=0; i<N; i=i+1) // N 個のインスタンスを生成
new(Receiver2, 50); // Receiver のインスタンス生成}
int RUN() {key = InKey();// キーコード入力
if (key == 0x1B) Stop();// ESC キーで終了
if ((key>='A') & (key <= 'Z'))// [A]-[Z]では特定のインスタンスSend(key-'@', 1);// 1 を送信
if ((key>='0') & (key<='9')) // [1]-[9]ではすべてのインスタンス
Send(0x7FFF, key);// key を送信}
int EVENT() {}}}

2002.1

Linux Japan87

る必要があるでしょう。

このときアドレスの指定と書き込む値の順番にいます。リスト2の main クラスは、共有メモ注意してください。RX レジスタに書き込む共リの 0 番地に 0 を格納した後、連続して 20 個3D アニメーションをリアルタイムに操作す有メモリの先頭アドレス、データ数を RY に設のReceiver2 のインスタンスを生成していまるために、「キーに割り当てたコマンドをメッ定してWrite("ファイル名")を実行すると、共すが、 Receiver2 のインスタンス番号は、異セージとしてインスタンスに送る」という方法

有メモリの内容を 32 ビット符号付き 10 進数となる値（1 から 20）で自動的に設定されます。

をJ3W付属のサンプルで常用しています。キーしてファイルに出力できます。RX レジスタに定数 N の値を変えるだけで区別可能なインス

入力を常に監視して、キー入力があれば、対応書き込む共有メモリの先頭アドレスを設定してタンスを好きなだけ生成できます。

するインスタンスにメッセージを送信すること

Read(" ファイル名 ")とすると32ビット符号付複数のインスタンスが1つのインスタンスにき 10 進数を共有メモリに読み込みことができ対して同時にメッセージを送る場合も考えられたくさんのメッセージや送信先のインスタンます。読み込んだデータ数は RY に返ります。

ます。複数のメッセージを受信した場合は、最スを数値で指定すると、プログラムが読みにくを専門に行うクラスを使います。

リスト 2 もリスト 1 と同じように動作しまも大きい値のメッセージ以外は廃棄されます。

くなります。プログラムが長くなったら、このすが、生成されるReceiver2 のインスタンス

また、メッセージの送出元のインスタンスは識連載の 2 回目（2 0 0 1 年 1 0 月号）で解説したは、共有メモリの 0 番地に格納されている値別できません。インスタンス間で同期しながらlibraryを使って数値に分かりやすい名前を付を使って自分でインスタンス番号を設定して複雑な動作をする場合は、共有メモリを使用すけるようにすべきでしょう。

j3w の内部構造の解説
前回（2001 年 12 月号）のコラムでは、J3W の 3Dエンジン部分を使って、回転する三角形を表示する実行を管理しています。イベントループがTJob::job_■仮想 CPU

プログラムmy_3d.oppを紹介しました。 my_3d.opp

scheduler()を呼び出すたびに、仮想CPUを順次実行して画面の更新を行います。 T J o b : : j o b _は、PCの速度に回転速度が依存するプログラムで、

j3wコマンドは、仮想CPUのエミュレータとして設計さscheduler()の呼び出し時には時刻を記録し、前回運動する物体は1つだけという簡単なものでした。物れています。三次元空間で形状を持ち、運動できる特殊な呼び出しを行った時刻との差の時間を引数として、活体を運動させているforループ部分を変更して、複数CPUです。3Dエンジンの機能に加えて、時間の管理と与動中の TProcess::Run を順に実行していきます。の物体が独立していろいろな動きをするようにするえられた命令の解析と実行が必要です。仮想CPUの命令

TProcessのインスタンスは、この経過時間分だけ位のは大変面倒です。さらに、速度の異なるPCでも物の実行はTProcessクラスが担当し、TJobクラスが複数の置などの状態を更新します。そしてすべてのインス体が同じ速さで動作するようにするには、J3Wでは仮想CPUの管理、時間の管理、仮想CPUが実行するコータンスの状態が更新された後、画面を更新します（図どうすればよいのでしょうか？

ドの格納を担当しています（図 A）。

B）。その他TJobクラスは、命令コードの格納、共有

今回は、複数の物体の動作の管理と、運動に「時間」の概念を追加する部分を見ていきます。

TJob クラス（図A 右）は、 TProcess のインスタンスへメモリの保持、グラフィック画面に表示する文字列のポインタを配列として持つことによって、 TProcessのと線分の情報、実行中のプロセス数などシステム全体のリソースを管理しています。

【図 A】TJob クラスと TProcess クラス

TProcess クラス（図 A 左）は、J3C 言語におけるインスタンスそのものです。J3Cのインスタンスごとに、TProcessクラスのインスタンスが生成されます。

TProcessクラスは仮想CPUのレジスタとデータメモリの領域を変数として持っています。仮想CPUの命
令コードは、 TJob クラスが 1 つだけ保持していて、TProcessクラスのインスタンスのRUNメソッドによって実行されます。移動や回転などの命令は、「10秒間前進」というように実行時間を指定できますが、TJobsから渡された時間内（PCの速度に依存しますが、10100msec 程度）では命令は終了しません。実行可能時間を消費した時点で、実行中の命令の中間状態を保持したまま制御を他のCPUに渡します。そして、次にCPUへ制御が渡されたときには、保存された中間状態から必要な動作を続行します。すなわち、その命【図 B】TJob クラスによる画面更新のタイミ
ング【図C】
移動距離または回転角度と実行時間の関係

令が終了する場合はプログラムカウンタを次の命令に進め、時間が足りなくて完全に終了する前に制御を返す場合には、前回終了した状態から実行を再開します（図 C）。
命令が消費する時間は、移動、回転など時間を渡す必要のある命令の場合は指定した時間を消費しますが、その他の多くの命令の実行時間は 0 になっています。従って、演算や分岐命令が連続して実行されることが保証されます。
J3Wの内部構造の解説は今回で終了です。次回のコラムはコンパイラであるj3cを解説する予定です。
（水谷純）

88

J3W for Linux 入門講座

し面倒ですが、（80×行）＋桁で計算できます。

ます（希望があれば多くします）。登録された線

文字は、gColorで表示色を設定し、gCursorで分は位置を書き換えるか、gLineClear で消去位置を設定した後に、g S t r i n g ( " 文字列 ") 、されるまで表示されたままになります。線分をフライトシミュレータではヘッドアップディ

gNumber(数値)、gChar（ 文字 ）で表示します。

表示する場合は、表 2 のようにレジスタ変数をスプレイ（高度や方向、ターゲットの位置を表

書き込み位置は自動的に更新されるので、書き込設定した後、gLine組み込み関数を実行します。

示する透明なスクリーン）を三次元空間を表示

みは続けて行えます。文字の位置は、フォントのリスト 4 では、10 度おきに 36 本の線分を放する画面に重ねて文字やグラフィックを表示す違いによって Windows 版のJ3W と比べると若干射状に描いています。線分の角度をθ、中心かることで実現しています。J3C 言語でも 3D ア左にずれます。

らの距離を始点 r1、終点 r2 としたとき、始点■グラフィック画面への直接描画

ニメーションに重ねてグラフィックウィンドウ

リスト3 は、1 秒間あたりの画面の書き換えの座標は(r1*cos θ , r1*sin θ)、終点の座に文字や線分を描画する関数を用意しています

回数（Frame Per Second）をグラフィックウィ

標は(r2*cos θ , r2*sin θ)となります（図（表 1）。

ンドウの左下に表示するクラスです。INIT メ3）。リスト4では、0番から35番の線分を少し線分を表示するには、左上隅が(0, 0) 、右ソッドで SystemTime()でミリ秒単位の時間、ずつ角度を変えて描き直すことによって計器風に回転させています。

下が(639, 479) の二次元の座標を使います。

SystemCount()で j3w を起動してからの画面グラフィックウィンドウの大きさは幅640ドッ

書き換え回数を取得します。RUNメソッドで描リスト3とリスト4のクラスを実行するためト、高さ480ドット固定で、変更することはで画回数／経過時間から FPS を計算して表示しのプログラムをリスト5に示します。このプログラムには三次元の物体がないため、筆者のきません。文字は、英数字（ASCII 文字）1 行ています。桁あふれ（画面書き換え回数が最近80 文字で 25 行の 2000 文字まで表示できます。のマシンでは大きくなるため）に注意して、小環境（Celeron 450MHz、RivaTNT、XFree86

日本語は表示できません。文字の表示位置の設数点以下 1 桁の精度で求めています。

4.0.3）では、1 秒間に 450 回の描画が行われて定は、左上隅が0、右下隅が1999の一次元の座ウィンドウに直接描画する線分は登録制となっいます（画面1）。CPUの無駄使いですね（^^；。

標で指定します。二次元で指定できないので少ていて、0 番から 99 番の 100 本までになっていfps.j3c をインポートして、適当な場所で【表 1】グラフィックウィンドウ描画関数【リスト 3】fps.j3c

関数名機能

gColor

グラフィックウィンドウの文字色を設定

gClear

グラフィックウィンドウの文字列
クリアgCursor

グラフィックウィンドウ文字カー
ソルの設定（0 〜 1999）

gPosition

グラフィックウィンドウの文字書
き込み位置取得

gNumber

レジスタの示す値をグラフィック

class fps {volatile int time, time0, time1;
volatile int count, count0, count1;
volatile int fps, fps0, fps1;
int start() {time0 = SystemTime();count0 = SystemCount();
Pause(300);}// 時間を記録// ループカウントを記録

ウィンドウへ出力
gChar

グラフィックウィンドウへ 1 文字の出力

gStringm

データメモリ文字列をグラフィッ
クウィンドウへ出力

gString

文字列をグラフィックウィンドウへ出力

gLine

グラフィックウィンドウのラインの描画設定

gLineClear

グラフィックウィンドウのラインの消去

int update_time() {gCursor(2 + 24 * 80);
time1 = SystemTime();time = time1 - time0;count1 = SystemCount();
count = count1 - count0;
count = count * 1000;
fps = count / (time / 10);
fps0 = fps/10;
fps1 = fps - fps0 * 10
gNumber(fps0);
gString(".");
gNumber(fps1);
gString(" FPS ");}【表 2】レジスタ変数
変数

意味

RX

始点 X 座標RY

始点 Y 座標

RZ

ライン色

RH

終点 X 座標

RP

終点 Y 座標

RB

ライン番号（0 〜 99）// 文字位置は(2, 24) から// 時間を記録// 経過時間を求める// 描画回数を求める// 10msec// 整数部// 小数点以下 1 桁// 数値を描画// 文字列を描画

int INIT() {start();}int RUN() {update_time();
Pause(100);}
int EVENT() {}}2002.1

Linux Japan89

new(fps, 50) を実行してインスタンスを起はグラフィック周りのベンチマークプログ

動すれば、どのプログラムにも他の部分に影ラムになります。

【図 3】線分の位置の割り出し響することなく FPS を表示する機能が簡単に付加できます。画面 2 は、先月号（2001 年 12■終わりに月 号 ）で 使 用 し た サ ン プ ル プ ロ グ ラ ムhierach.j3c に fps.j3c を加えて実行した例

キーボードから複数の物体に情報を伝えです。今月号の付録CD-ROMにhierach2.j3cたり、文字情報を表示したり、3Dアプリケーとして収録しています。500頂点程度の物体をションを作成する準備は整いました。次回表示しても、90FPS（筆者の環境）と、結構高は 3 次元空間の見方、「視点」について考え速に動作していることが確認できます。これてみる予定です。

【画面 1】リスト 5（main_2d.j3c）の実行結果【リスト 4】rad_line.j3cclass RadialLine {volatile int n;volatile int m;
int Line2D(int n, int color, int x1, int y1, int x2, int y2) {PushRegisters();// レジスタ変数を保存
RB = n;// ライン番号（0 〜 99）
RZ = color;// ラインの色
RX = x1 + 320; RY = y1 + 240;// 始点 画面の中心を 0 とする
RH = x2 + 320; RP = y2 + 240;// 終点
gLine(); // 画面にラインを描画
PopRegisters();// レジスタ変数を復帰}【画面 2】回転体の物体に fps.j3c をインポートした例

int RadialLine(int color, int r0, int r1, int d) {volatile int i, cosA, sinA;for (i=0; i<36; i=i+1) {// 10 度ごとに放射状のライン
cosA = cos(i * 80 + d * 8);sinA = sin(i * 80 + d * 8);
if (i - i / 9 * 9 == 0)// 90 度の倍数の場合は長く
Line2D(i, color-1, cosA * (r0-10)/10000, sinA *(r0-10)/10000,cosA * (r1+10)/10000, sinA * (r1+10)/10000)
else
Line2D(i, color, cosA * r0 / 10000, sinA * r0 / 10000,cosA * r1 / 10000, sinA * r1 / 10000)}}
int INIT() {n = 0;m = 2;
gColor(7);}【リスト 5】main_2d.j3c

import "rad_line.j3c";
import "fps.j3c";// 文字色は白

int RUN() {RadialLine(7, 80, 100, n);gCursor(13*80+38); // 画面中心付近に文字描画
gString("Angle:");gNumber(n);gString(" "); // 数値の桁数が小さい場合の対策
if (n >= 180) m = -2;
if (n < -180) m = 2;n = n + m;
Pause(20);}class main {volatile int key;
int INIT() {new(RadialLine, 50); // インスタンスの生成
new(fps, 50);
BackgroundColor(0x000); // 背景色を設定
GraphMode();// グラフィックウィンドウを開く}
int RUN() {key = InKey();// キーコード入力
if (key == 0x1B) Stop();// ESC キーで終了}
int EVENT() {}int EVENT() { }}90J3W for Linux 入門講座}