X Window System, Client Programming, No.7


diff -c x14b.c x14be.c(TrueColorディスプレイ対応版)
*** x14b.c	Mon May 22 19:59:08 2006
--- x14be.c	Mon May 22 19:59:20 2006
***************
*** 16,21 ****
--- 16,59 ----
  GC gc;
  XColor defs[RSIZE][GSIZE][BSIZE];
  XImage *image, *image2;
+ int use_truecolor=0;
+ int nshift_red, nshift_green, nshift_blue;
+ 
+ static int getshiftbit(unsigned long mask) {
+     int i, n;
+     for (i=0; i<sizeof(mask)*8; i++) {
+         if (mask & 1) {
+             if (mask == 0xff) return(i);
+             else return(-1);
+         }
+         mask >>= 1;
+     }
+     return(-1);
+ }
+ 
+ int check24() {
+   if (DefaultDepth(dpy,scr) == 24) {
+         XVisualInfo xvit, *xvi;
+         int n, i;
+         xvit.depth = 24;
+         xvit.class = TrueColor;
+         xvi = XGetVisualInfo(dpy,VisualDepthMask|VisualClassMask,&xvit,&n);
+         if (xvi) {
+             for (i=0; i<n; i++) {
+                 nshift_red = getshiftbit(xvi->red_mask);
+                 nshift_green = getshiftbit(xvi->green_mask);
+                 nshift_blue = getshiftbit(xvi->blue_mask);
+                 if (nshift_red >= 0 && nshift_green >= 0
+                     && nshift_blue >= 0) {
+                     use_truecolor=1;
+ 		    return(1);
+                 }
+                 xvi++;
+             }
+         }
+     }
+     return(0);
+ }
  
  XImage *init_ximage(int w,int h) {
      int format, bitmap_pad, depth;
***************
*** 50,55 ****
--- 88,95 ----
      Colormap cmap;
      int i,j,k;
  
+     check24();
+     if (use_truecolor) return;
      cmap=DefaultColormap(dpy,scr);
      for (i=0; i<RSIZE; ++i) {
  	for (j=0; j<GSIZE; ++j) {
***************
*** 112,127 ****
  void set_rgb(XImage *image,int x0,int y0,unsigned char *data,int w,int h) {
      int x,y;
      unsigned int r,g,b;
!     unsigned long red,green,blue;
      unsigned char *s=data;
  
      for (y=y0; y<y0+h; ++y) {
  	for (x=x0; x<x0+w; ++x) {
  	    red = *s++; green = *s++; blue = *s++;
! 	    r = dither(red,RSIZE-1,x,y);
! 	    g = dither(green,GSIZE-1,x,y);
! 	    b = dither(blue,BSIZE-1,x,y);
! 	    XPutPixel(image,x,y,defs[r][g][b].pixel);
  	}
      }
  }
--- 152,173 ----
  void set_rgb(XImage *image,int x0,int y0,unsigned char *data,int w,int h) {
      int x,y;
      unsigned int r,g,b;
!     unsigned long red,green,blue,pxl;
      unsigned char *s=data;
  
      for (y=y0; y<y0+h; ++y) {
  	for (x=x0; x<x0+w; ++x) {
  	    red = *s++; green = *s++; blue = *s++;
! 	    if (use_truecolor) {
! 		pxl = (red << nshift_red) + (green << nshift_green)
! 		    + (blue << nshift_blue);
! 	    } else {
! 		r = dither(red,RSIZE-1,x,y);
! 		g = dither(green,GSIZE-1,x,y);
! 		b = dither(blue,BSIZE-1,x,y);
! 		pxl = defs[r][g][b].pixel;
! 	    }
! 	    XPutPixel(image,x,y,pxl);
  	}
      }
  }


x14be.cの実行
PROMPT$ <I>cp ~nitta/pro2w/x14be.c x14be.c</I> <IMG SRC="/local-icons/enter.gif">
PROMPT$ <I>gcc -I/usr/X11R6/include x14be.c -o x14be -L/usr/X11R6/lib <FONT COLOR=red>\</FONT>
              -lX11 -lsocket -lnsl</I> <IMG SRC="/local-icons/enter.gif"> ← Solaris2.Xの豺
    届淋 地模匳鱚筅苳ぢ行末の\記号は「,旅圓眤海韻ぢ行にタイプすべきである」ことを表しています。</FONT>
PROMPT$ <I>gcc -I/usr/X11R6/include x14be.c -o x14be -L/usr/X11R6/lib -lX11</I> <IMG SRC="/local-icons/enter.gif">
                            ← Linuxなどの豺
侑詫來髭苳侍唄矼  ㊥ 隰鱧 ㊥聲竇窺鱧皃踉晒髭苳浜嘔箪跫竅讚蜒闔鶩緕鬯芍罌
害因牡ぢの大きさの画像 tmp.rgb の紊髻蔚ぢの大きさの画像 face1.rgb が動く

実時間動作するクライアント

先週までは、イベント駆動型のクライアントの作成方法に ついて説明してきました。 今週は、実時間動作を行なうクライアントの作成方法に 関して説明します。

イベントの送出

X Window System ではサーバとクライアントの間の通信効率を上げるために、 クライアントはできるだけ要求をまとめて X サーバに送り出そうとします。 したがって、Xlib の関数を呼び出したからといってその瞬間に Xサーバに要求が送られるわけではありません。 このため、「関数を呼び出したタイミング」と、 「その関数によって生成された要求がXサーバに実際に 送られるタイミング」 とがずれてしまいます。

XクライアントがXサーバに送る要求は クライアントのバッファに一時的に蓄えられていて、

という3種類の場合に実際に送り出されます。

そのため、ある瞬間にXサーバの要求が送り出されることを 保証するためには、バッファ内の要求を送り出す関数である

などを呼び出す必要があります。

リアルタイム動作するプログラム

X Window Systemのアプリケーションの多くは、 「イベントが送られて来るまでずっと待ち、 送られてきたらそのイベントを処理する」という 『イベント駆動型 (event-driven)』の動作を行います。

イベント駆動型のプログラム
int main() {
    ...
    for (;;) {
        XNextEvent(dpy,&ev);
        switch (ev.type) {
            ...
        }
    }
}

一方、反射神経ゲームのように実時間動作するプログラムでは、 イベントがこなくても一定時間ごとに次の状態に移るための 計算を行なう必要があります。 このために「一定時間が経過するのを待つ(時間をつぶす)」 必要がありますが、時間待ちの間は一旦制御をOSに帰すべきです。 「ある時刻が来るのを待つ」ときに「動作時刻が来るまで 単に時間を調べながらループを回る」ようなことは マルチタスク環境では決して行ってはいけません。 単純なループを回るだけのプログラムは、CPU時間を大量に浪費し、 他のアプリケーションの実行に悪影響を与えます。 残念なことに、ときどきX Window System上のツールやゲーム などでいい加減な作り方をされたものを見掛けることがありますが、 そのような質の低いプログラムは自分は書かないようにしましょう。

待ち時間の間一旦制御をOSに帰すには

などの方法が考えられます。

select()を使った場合は以下のようになります。

selectを使って待つ
#include <sys/types.h>
#include <sys/time.h>

#define TIME_SPAN	1.5

double gettime() {
    struct timeval t;
    gettimeofday(&t,NULL);
    return(t.tv_sec + t.tv_usec * 1e-6);
}

void main() {
    Display *dpy;
    Window win;
    GC gc;
    fd_set readfds, writefds, exceptfds;
    struct timeval timeout;
    double t1,t2,t3;
    int i;
    ...
    t1 = gettime();
    for (;;) {
	XEvent ev;
	FD_ZERO(&readfds);
	FD_ZERO(&writefds);
	FD_ZERO(&exceptfds);
	FD_SET(ConnectionNumber(dpy),&readfds);
	t2=gettime();
	t3 = TIME_SPAN - (t2 - t1);
	if (t3 < 0) t3 = 0;
	timeout.tv_sec= t3;
	timeout.tv_usec= (t3-timeout.tv_sec)*1e6;
	i=select(getdtablesize(),&readfds,&writefds,&exceptfds,&timeout);
	t2=gettime();
	if (i<0) { エラー処理 }
	if (i==0 || (t2-t1) > TIME_SPAN) {
	    /* タイムアウトが起きたので状態を進める*/
	    ...;
	    t1 = gettime();
	}
	while (XCheckMaskEvent(dpy,-1,&ev) == True) {
	    switch (ev.type) {
		...
	    }
	}
    }
}

Xサーバとの通信に使っているソケットは ConnectionNumber(dpy) で 得ることができます。本来は select() で待つと Xサーバからの イベント・パケットが存在するかどうかは以下のようにして 得られるはずです。

xselect1.c
    FD_SET(ConnectionNumber(dpy),&readfds);
    i = select(...,&readfds,...);
    if (i > 0) {
        if (FD_ISSET(&readfds,ConnectionNumber(dpy))) {
            /* Xサーバからイベントが送られてきた */
            XNextEvent(dpy,&ev);
	    switch (ev.type) {
                ...
            }
	)
        ...
    }

しかし、システムによってはサーバから送られてきたイベントを できるだけ読み込んで(readして)しまうことがありますので (SystemV系のUnixなど)、 XNextEvent()などブロックする可能性のある関数を呼び出すよりは、 XCheckIfEvent(), XCheckMaskEvent(), XCheckTypedWindowEvent(), XCheckWindowEvent()などブロックしない関数を用いるほうがよい ようです。

xselect2.c
    FD_SET(ConnectionNumber(dpy),&readfds);
    i = select(...);
    /* iの値によらず XCheckMaskEvent() を呼び出す */
    while (XCheckMaskEvent(dpy,-1,&ev) == True) {
        switch (ev.type) {
            ...
        }
    }

ネットワークを利用したプログラム

select() を使ってリアルタイム動作を行うプログラムを ネットワーク対応にするのは比較的簡単です。 この講義の冬学期前半(第1回〜第5回)で使った tcp.c を 利用して説明します。

realtime2.c
void main() {
    Display *dpy;
    Window win;
    GC gc;
    fd_set readfds, writefds, exceptfds;
    struct timeval timeout;
    double t1,t2,t3;
    int i,j,sock;
    ...
    sock = init_client(ホスト名,ポート番号)
    t1 = gettime();
    for (;;) {
	XEvent ev;
	FD_ZERO(&readfds);
	FD_ZERO(&writefds);
	FD_ZERO(&exceptfds);
	FD_SET(ConnectionNumber(dpy),&readfds);
	FD_SET(sock,&readfds);
	t2=gettime();
	t3 = TIME_SPAN - (t2 - t1);
	if (t3 < 0) t3 = 0;
	timeout.tv_sec= t3;
	timeout.tv_usec= (t3-timeout.tv_sec)*1e6;
	i=select(getdtablesize(),&readfds,&writefds,&exceptfds,&timeout);
	t2=gettime();
	if (i<0) { エラー処理 }
	if (i==0 || (t2-t1) > TIME_SPAN) {
	    /* タイムアウトが起きたので状態を進める*/
	    ...;
	    t1 = gettime();
	}
	if (i>0 && FD_ISSET(sock,&readfds)) {  /* サーバからデータが送られてきた */
	    j=read(sock,バッファ,バッファの長さ);
	    ...                 /* データを処理する */
	}
	while (XCheckMaskEvent(dpy,-1,&ev) == True) {
	    switch (ev.type) {
		...
	    }
	}
    }
}


演習(12)


課題1

[1] 先週作成した k14b.c を変更し、さらに別の16x16ドット程度の 画像が0.5秒毎にランダム・ウォーク(16ドット程度ずつ乱数で上下左右に 移動してゆく)するように変更して下さい。 ファイル名は x15.c としましょう。


(ヒント)乱数を発生させるには以下のようにします。

rand.c
#include <stdlib.h>
#include <time.h>

main() {
    time_t t;
    
    time(&t);
    srand(t);
    ...
    i = rand();   /* 乱数の発生 */
}

(ヒント2)上下左右のランダムウォークを決定するには、発生させた乱数を 4で割った余りで判断すればよいでしょう。

rand2.c
    switch (rand() % 4) {
      case 0: ....; break;   /* up */
      case 1: ....; break;   /* down */
      case 2: ....; break;   /* left */
      case 3: ....; break;   /* right */
    }

[提出物]

課題が完成したら、x15.c を Subject: に Report と書いて p2r13@nw.tsuda.ac.jpへ送って下さい。 コンパイルできなかったり、きちんと動作しないものを送っても 提出とは認めません。

上記の Email アドレス宛に送ったメイルは http://nw.tsuda.ac.jp/cgi-bin/cgiwrap/p2r13/mailhead でヘッダ部が参照できますので、Emailを送ったあとで 正しく提出されたかどうか必ず確認しておいて下さい。

提出期限は来週木曜日 8:50a.m. です。


Yoshihisa Nitta

http://nw.tsuda.ac.jp/