スケジューラと低い優先度のキューについて

Max には、いくつかの実行スレッドがあります。これらのスレッドに関する詳細については、Max のドキュメント、および「Max におけるイベントの優先度(スケジューラ vs キュー)」で述べられています。この章では、これらすべてをカバーするのではなく、スケジューラ(オーバードライブが実行されている場合、他の高い優先度を持つスレッド実行されます)と低い優先度のキュー(これは常にメインのアプリケーションスレッドで実行されます)に限定して説明したいと思います。Jitter に関する限り、リアルタイムオーディオスレッドや、このリアルタイムオーディオスレッドでスケジューラが実行されるオーディオ割り込みの場合は考慮しません。

デフォルトでは、Jitter はすべての描画とマトリックス処理をメインアプリケーションスレッドで実行します。これは、低い優先度のキューによって提供されるイベントを用いて実行されます。低い優先度で処理される理由は、ノートのトリガやオーディオ DSP のような高いタイミングイベントにビジュアル処理によるタイミングの問題が生じるのを防ぐためです。Jitter はまた、リアルタイムで演算を行なうには多すぎる処理が要求されるインスタンスで、ビジュアルのストリームの時間的なダウンサンプリングをうまく実行するメカニズムとして、低い優先度キューを利用します。この結果、要求に応じられない場合、フレーム落ちが生じます。オーディオでは、サンプルのフレーム落ちは、クリックノイズとして聞こえるため適当ではありませんが、動画では、予定されたサンプリングレートで新しい画像が生成されない場合でも、その直前の画像が持続されます。

Defer と Usurp

低い優先度のキューから Jitter の描画とマトリクス処理を実行するメカニズムを、"defer" と "usrup" と呼びます。defer メカニズムは任意の高い優先度を持つイベントを受け取り、対応する低い優先度のイベントを生成してこれを低い優先度のキューの最後に置きます。defer メカニズムでは、確実にイベントが高い優先度のスケジューラスレッドで実行されないようにしますが、前に述べたような時間的なダウンサンプリングによってスケジューラが残ることを防止しません。これを行なうためには、usurp メカニズムを使用しなければなりません。usurp メカニズムは要求されたタスクのための低い優先度のキューエレメントを1つ(メソッド呼び出し、またはアトリビュートセッター)しか使用しません。userp の動作は次のようなものです。メソッド、またはアトリビュートの呼び出しを行なう保留状態のイベントがない場合、新しいイベントは低い優先度のキューの最後に置かれます。すでに保留状態のイベントが存在する場合には、userp メカニズムは新しいイベントを低い優先度のキューに置くのではなく、メソッドやアトリビュートの呼び出しの際に渡すための引数を「奪い」ます。この方法では、仮に低い優先度のメトロノームが急速にアトリビュートをセットするための値を送り出している場合、最初の低い優先度のイベントは処理を待ち続けているままですが、セットされる値は絶えず更新され(userp : 奪い取られ)、イベントが実行される時に保持していた値のみが使用されます。defer および usurp メカニズムは、Max パッチャー内からの呼び出しの場合にのみ動作するという点を覚えておくことは重要です。C、Java、Javascript などのテキストベースのプログラミング言語から呼び出されるメソッドではすべて、この defer や usurp メカニズムは無視されます。このような呼び出しをテキストベースのプログラミング言語から行ない、かつ deferusurp のような処理が必要となる場合には、十分注意を払いながらあなた自身でこれを行なう必要があります。

Jitter Object メソッドでの Defer と Usurp の使用

Jitter でメソッドを定義する場合、Max/MSP のメソッドで行なう場合と同様、型を表す定数を使って定義を行なうことができます。代表的な型定数には A_LONGA_FLOATA_SYMBOL といった 通常の atom 要素や、これに対応し、デフォルト値を持った A_DEFLONGA_DEFFLOATA_DEFSYM があります。また、可変引数用の A_GIMME があり、これは atom のリストと atom の数を提供します。さらに、プライベートで型を持たない A_CANT があり、これはパッチャーに引数を公開せず、呼び出しのために、付加的なC の関数プロトタイプ情報を必要とします。これらの型定数 はJitter オブジェクトの中でも使用することができますが、ほとんどのメソッドは defer あるいは usurp メカニズムを利用するパッチャーインターフェイスに公開されるために、A_DEFER_LOW および A_USURP_LOW という2つの型定数によって定義されます。A_DEFER_LOW あるいは A_USURP_LOW 型定数で定義されたメソッドは、A_GIMME メソッドと同じ 引数プロトタイプを持つようにしなければなりませんが、裏では Jitter は deferusurp メカニズムを利用し、適切な処理を行なっています。次の jit.gl.vedeoplane からの2つのメソッドの例では、これらのメカニズムを使用しています。

// userp を使った jit matrix メソッドを追加します。 jit_class_addmethod(_jit_gl_videoplane_class, (method)jit_gl_videoplane_jit_matrix, "jit_matrix", A_USURP_LOW, 0);

// defer を使った sendtexture メソッドを追加します。 jit_class_addmethod(_jit_gl_videoplane_class, (method)jit_gl_videoplane_sendtexture, "sendtexture", A_DEFER_LOW, 0);

次はこれらのメソッドの実装例です。

void jit_gl_videoplane_jit_matrix(t_jit_gl_videoplane *x, t_symbol *s, int argc, t_atom *argv) { t_symbol *name; void *m; t_jit_matrix_info info; long dim[2]; if ((name=jit_atom_getsym(argv)) != _jit_sym_nothing) { m = jit_object_findregistered(name); if (!m) { error("jit.gl.videoplane: couldn't get matrix object!"); return; } } if (x->texture) { jit_object_method(m, _jit_sym_getinfo, &info); jit_attr_getlong_array(x->texture,_jit_sym_dim,2,dim); jit_object_method(x->texture,s,s,argc,argv); jit_attr_setsym(x,ps_texture,x->texturename); } }

void jit_gl_videoplane_sendtexture(t_jit_gl_videoplane *x, t_symbol *s, int argc, t_atom *argv) { if (x->texture) { s = jit_atom_getsym(argv); argc--; if (argc) argv++; else argv = NULL; object_method_typed(x->texture,s,argc,argv,NULL); } }

ヘッダファイルを調べて見ると、A_DEFER および A_USURP という型定数もあることに気がつくでしょう。しかし、これらはイベントを低い優先度のキューの先頭に置く遅延方法を採用していて、メッセージの順序を乱してしまう可能性があるため、旧式の方法であると考えるべきでしょう。

Jitter オブジェクトのアトリビュートでの Defer と Usurp の使用

メソッドと異なり、アトリビュートはゲッター、セッターといったアクセサメソッドで型定数を使用することはありません。その代わりに、これらのメソッドでは常に A_GIMME と同じ形のプロトタイプを持ちますが、伝統的な A_GIMME 定数のメソッドシンボルポインタの代わりに、アトリビュートオブジェクトが渡されます。このため、アトリビュートのアクセサで deferusurp メカニズムを使用できるようにするためには、アトリビュートコンストラクタへのアトリビュートフラグ引数を通して行なうという方法を採ります。ゲッターアクセサメソッドでは、JIT_ATTR_GET_DEFER_LOW または、 JIT_ATTR_GET_USURP_LOW フラグを使用し、セッターアクセサメソッドでは、JIT_ATTR_SET_DEFER_LOW または、 JIT_ATTR_SET_USURP_ROW フラグを使用します。次の例は、jit.gl.videoplane の中の アトリビュート定義です。

attrflags = JIT_ATTR_GET_DEFER_LOW | JIT_ATTR_SET_USURP_LOW; attr = jit_object_new(_jit_sym_jit_attr_offset,"displaylist", _jit_sym_char,attrflags,(method)0L, (method)jit_gl_videoplane_displaylist, calcoffset(t_jit_gl_videoplane, displaylist)); jit_class_addattr(_jit_gl_videoplane_class,attr);

上記のコード例では、defer メカニズムを使用するゲッターアクセサ(JIT_STTR_GET_DEFER_LOW) と usurp を使用するセッターアクセサ(JIT_ATTR_SET_USURP_LOW)が定義されていますが、これらの Jitter オブジェクトアトリビュートがプライベートではなく定義されていることに気がついたかもしれません。これは、Jitter オブジェクトのアトリビュートをパッチャーに公開するスタイルで、推奨されるものです。その理由は、高い優先度でアトリビュートが繰り返し設定されるケースは数多くあるためで、次のようなことを両方とも行なう必要があるからです。1つは、最後の高い優先度の値は次の演算のときに低い優先度にすること、もう1つは、低い優先度で処理できる能力を超えて高い優先度でより多くのイベントが生成されることによって生じる低い優先度のキューの残りがないようにすることです。 defer メカニズムをゲッターメソッドで使用すれば、すべてのアトリビュートの問合せの結果は、対応するメッセージとしてダンプアウトアウトレットから出力されます。これを行なわない場合、一部のパッチャーのロジックはすぐに混乱してしまいます。Max プログラマが違った動作を必要とする場合には、jit.gball オブジェクトを利用し、メッセージのストリームに対して defer または usurp メカニズムを強制的に適用させることができます。

Max ラッパーオブジェクトでの Defer と Usurp の使用

今まで述べてきたような方法はMax ラッパーオブジェクトでメソッドやアトリビュートを宣言する場合にも当てはまりますが、関数の呼び出しに関して若干の違いがあります。Max ラッパーオブジェクトでは、メソッドの場合にはmax_addmethod_defer_low()max_addmethod_usurp_low() 、アトリビュートの場合には、max_jit_classex_addattr() という特別な Max のオブジェクト関数呼び出しを使用しなければなりません。次の例は jit.matrixset からのものです。max_addmethod_defer_low() および max_addmethod_usurp_low() のどちらの場合においても、型定数が提供されていない点に注意して下さい。

// defer 処理を施される "exportmovie" メソッドを追加します max_addmethod_defer_low((method)max_jit_matrixset_export_movie, "exportmovie"); // usurp 処理を施される outputmatrix メソッドを追加します max_addmethod_usurp_low((method)max_jit_matrixset_outputmatrix, "outputmatrix"); // index アトリビュートを追加します attrflags = JIT_ATTR_GET_DEFER_LOW | JIT_ATTR_SET_USURP_LOW ; attr = jit_object_new(_jit_sym_jit_attr_offset," index",_jit_sym_long,attrflags, (method)0L,(method)0L, calcoffset(t_max_jit_matrixset,index)); max_jit_classex_addattr(p,attr);

Usurpメカニズムを使用しないほうがよい場合

Jitter の MOP オブジェクトでは、リアルタイム処理で扱うことができる能力を超えて多くの bang メソッドが送信される場合、usurp メカニズムを使用してフレームを落とします。しかし、jit.gl.render bang メソッドでは、usurp ではなく、 defer メカニズムを使用します。これは意外に感じられるかもしれませんが、jit.gl.render による OpenGL のレンダリングのでは、複数のメッセージによるメッセージのグループを使って、画像の消去や、オブジェクトのあらゆる自動的でない描画を行ない、その後、bang メソッドによって自動的なクライアントの描画とスクリーンへのスワップ処理を行ないます。これらはアトミックアクション(一連の不可分な動作として扱われる処理)ではありません。(すなわち、1つのイベントではなく、一連の様々なイベントを必要とします)。usurp メカニズムは usurp 処理を行なわれるイベントに関係するメソッドやアトリビュートの仕様であるため、アトミックアクションでのみ動作します。このため、フレームを落とすような何らかの処理を実行する場合、これをメッセージシーケンスのトリガの前に行なうことがユーザにとって重要です。通常、qmetrojit.qball を使ってこのような処理を行ないます。あなたのオブジェクトが、jit.gl.render と同じように一連のイベントのグループを必要とする処理を行なう場合には、その処理に関連したメソッドでは usurp ではなく、defer メカニズムを使うほうがベストです。

Defer と Usurp のオーバーライド

Jitter マトリックスを低い優先度で実行するように限定することをユーザが望まないインスタンスがあります。リアルタイムイメージ処理以外の処理、例えばパラメータ補間やオーディオデータを持ったマトリックスによるタスクで、マトリックスを使用する場合などがこれにあたります。このようなタスクのために jit.qfaker オブジェクトが提供されていますが、これは、低い優先度を使ったメカニズムを回避することによる潜在的な問題の発生について熟知した上級ユーザのためのものです。
すでに述べたように、テキストベースの言語によるプログラミングでは、低い優先度を使ったメカニズムは使用されず、すべてのメソッド、およびアトリビュートのアクセサの呼び出しは同時に発生します。したがって、通常、テキストベースの言語からこれらのメソッドのオーバーライドを考える必要はありません。しかし、jit.qfaker の動作をシミュレートしようとするような特定のオブジェクトで使用できるように、usurpdefer のための Jitter によるキュー状態の探索をオーバーライドする max_jit_queuestate() 関数が用意されています。さらに、jitter がキューの状態をどのように把握しているかを、max_jit_getqueuestate() 関数によって問い合わせることも可能です。この関数は deferusurp メカニズムによって使用されるものです。これらの関数のためのソースコードを次に示しますので、参考にして下さい。

long max_jit_queuestate(long state) { long rv =_max_jit_queuestate; _max_jit_queuestate = (state!=0); return rv; } long max_jit_getqueuestate(void) { // この if によって、常に true を返します if (_max_jit_queuestate) return 1; return !sched_isinpoll(); }