Max 5 API Reference
Max のスケジューラによって、処理の実行タイミングを遅れさせることができます。
スケジューラは時間の経過を倍精度で管理しますが、スケジューラの解像度はユーザの環境設定に依存します。スケジューラはまた、低い優先度のキューと連携して動作します。この低い優先度のキューは、時間のかかる操作がスケジューラに組み込まれた場合に、タイミングの精度を崩さない方法でこれを実行することができるようにするものです。
ほとんどのオブジェクトは、スケジューラとのインターフェイスとして clock ( t_clock ) オブジェクトを使用します。clock は task 関数と関連しています。 task 関数はスケジューラの現在時刻が clock の時刻に達した場合に処理を実行します。また、 schedule() という関数もありますが、これを使うと、ある関数の実行を1回だけ遅れさせることができます。 schedule()は、この処理を行なうために clock を作成しますが、オブジェクトで繰り返しスケジューラを使用したい場合には、作成された clock への参照を保存しておき、clock を再利用できるようにしたほうがより効率的です。
スケジューラは clock タスク(task)を実行する必要があるかどうかを確かめるために、周期的にポーリングを行なっています。このポーリングをいつ、どのくらいの頻度で行なうかは、Max ユーザが設定できる多くの初期設定によって決まります。まとめると次のようになります。
スロット(Throttle)および、インターバル(Interval)の設定は、同様に低い優先度のキューにも存在します。
詳細は「 Timing 」ドキュメントを参照して下さい。詳細を見ると、一見膨大な内容に感じられるかもしれませんが、重要なことは、スケジュールしたタスク(task)が実行される正確なタイミングが変動するという点です。Max では、特定のアプリケーションで必要なすべての演算のバランスを取るために、ユーザによるスケジューラのコントロールがこのようなレベルで行えるようになっています。
1. オブジェクトのデータ構造体に、clock オブジェクトへのポインタを保持するためのメンバを追加します。
2. clock が実行されるときに何らかの処理を行なうためのタスク( task )関数を書きます。この関数は唯一、オブジェクト自身へのポインタを引数として取ります。次の例は、スケジューラの現在時刻を取得し、それを表示するものです。
void myobject_task(t_myobject *x) { double time; sched_getftime(&time); post("instance %lx is executing at time %.2f", x, time); }
3. インスタンス生成ルーチン(new instance ルーチン)の中で、clock を作成します(オブジェクト自身へのポインタ、および task 関数を渡します)。そして、その結果をオブジェクトのデータ構造体に保存します。
4.clockをスケジュールします。clock のスケジュールには clock_fdelay() を使用し、現在時刻からの遅延という形で設定します。次の例は、cloack が 現在時刻より100 ミリ秒後に実行されるようスケジュールするものです。
clock_fdelay(x->m_clock, 100.);
何らかの理由で clock の実行をキャンセルする必要がある場合、 clock_unset() を使ってキャンセル処理を行なうことができます。
clock_unset(x->m_clock);
5. オブジェクトのインスタンス消滅ルーチン(free ルーチン)で clock を解放します。
object_free(x->m_clock);
すでに設定されている clock で clock_delay() を呼び出した場合、実行時間が変更されるという点に注意して下さい。この場合、2回実行されるわけではありません。
qelem (キューエレメント:"queue element")は、処理が低い優先度のスレッドで実行されることを確実にするために使用されます。 t_qelem に関連づけられたタスク(task)関数は、低い優先度のキューに処理が渡されたときに実行されます。このキューは、常にメインスレッド(ユーザインターフェイス・スレッド)で処理されます。「セット」された qelem は 常に低い優先度のキューに置かれ、キューに処理が渡されるとただちに実行されます。
高い優先度での実行を避ける必要がある処理は、主に次の2つです。 1つは、時間がかかったり予測できない結果を招く可能性があるもので、ファイルアクセスなどがその例です。もう1つは、ある時間だけ処理の実行をブロックするようなもので、例えばダイアログボックスの表示(ファイルダイアログを含みます)がこれにあたります。
qelem 使用するための方法は、clock を使用する場合とよく似ています。
1. オブジェクトのデータ構造体に、qelem へのポインタを保持するためのメンバを追加します。
typedef struct _myobject { t_object m_obj; void *m_qelem } t_myobject;
2. qelem が実行されるときに何らかの処理を行なうための タスク(task)関数を書きます。この関数は唯一、オブジェクト自身へのポインタを引数として取ります。
void myobject_qtask(t_myobject *x) { post("I am being executed a low priority!" }
3. インスタンス生成ルーチン(new instance ルーチン)の中で、qelem を作成します(オブジェクト自身へのポインタ、および task 関数を渡します)。そして、その結果をオブジェクトのデータ構造体に保存します。
4. qelem_set() を使って qelem をセットします。例えば、 qelem_set() を clock タスク(task)関数 の中で呼び出すか、bang や int のようなメッセージに対する応答の中で呼び出します。
qelem_set(x->m_qelem);
何らかの理由で qelem の実行をキャンセルする必要がある場合、 qelem_unset() を使ってキャンセル処理を行なうことができます。
qelem_unset(x->m_qelem);
5. インスタンス消滅ルーチン(free ルーチン)の中で qelem_free()を呼び出します。 object_free() や freeobject() を呼び出さないで下さい。clock と違って qelem はオブジェクトではありません。
qelem_free(x->m_qelem);
すでに設定済みの qelem で qelem_set() を呼び出しても、2回実行されるわけではないことに注意して下さい。これは本来の機能で、バグではありません。これにより、低い優先度を持つタスクを、タスクがトリガされる可能性がある高い優先度の速さではなく、低い優先度の処理による速さでだけ実行させることが可能になります。ナンバーボックスの再表示が counter の値の変化よりも遅く行なわれる場合などは、その例と言えるでしょう。UI オブジェクトを書いている場合でも、このことについて心配する必要はありません。 Max はこの処理を内部で(qelem を使って)処理してくれます。
defer 関数や、これと同様な機能を持つ関数は、qelem を使って関数の実行が確実に低い優先度で行なわれるようにします。defer の仲間には、 defer()、 defer_low()、defer_midium() の3種類があります。defer() を使用する場合と qelem を使用する場合の違いは、 defer() が1回限りのものであるという点です。defer はqelemを作り、セットし、タスク関数の実行が完了した後にこれを削除します。これによる効果は次のようなものです。高い優先度のイベントがあり、このイベントが低い優先度で実行される何らかの処理をトリガする必要がある場合、 defer() は高い優先度のイベントが起こるたびごとに、この低い優先度のタスクが(1:1の比率で)実行されることを保証します。これに対し、qelem を使用すると、低い優先度のキューに処理が移る時間間隔に対応した速さでのみタスクが実行されます。したがって、 defer()による処理を非常に速いスピードで繰り返した場合、低い優先度のキューには処理が溜まった状態になり、UI の応答が遅くなってしまいます。
defer() を使用する典型的な場合として、オブジェクトが、ユーザにファイルを開くよう促すための read メッセージを実装している場合を挙げることができます。タイマ・スレッドでダイアログを開き、ユーザの入力を待つようなパッチはおそらくクラッシュを引き起こすでしょう。クラッシュを引き起こさない場合でも、実際上、スケジューラは停止してしまいます。
defer() を使用するには、まず defer によって処理されるタスク関数(実行を遅延させるタスク関数)を書きます。このタスク関数は低い優先度で実行されるものです。タスク関数には、オブジェクトへ自体へのポインタを渡します。さらに、anything メソッドのプロトタイプの形式でシンボル、およびアトムのリストを渡しますが、これらの引数が必要でなければ渡さなくても構いません。
void myobject_deferredtask(t_myobject *x, t_symbol *s, long argc, t_atom *argv) { post("I am deferred"); }
次に示すように、 defer() を使ってこのタスク関数を呼び出します。最初の例は引数を渡さない場合、2番目の例は long の値を持つアトムを2つ渡す場合です。
defer((t_object *)x, (method)myobject_deferredtask, NULL, 0, NULL); t_atom av[2]; atom_setlong(av, 1); atom_setlong(av+ 2, 74); defer((t_object *)x, (method)myobject_deferredtask, NULL, 2, av);
defer は、渡されたすべてのアトムを新規に割り当てられたメモリにコピーします。このメモリは、defer が処理するタスクが実行されるときに解放されます。
高い優先度で実行された場合、 defer() は処理するタスク(実行を遅延させるタスク)を低い優先度のキューの先頭に置きます.高い優先度で実行されたのでなければ、 defer() は処理するタスクを直ちに呼び出します。
どのような優先度で実行された場合でも、 defer_low() は処理するタスクを低い優先度のキューの最後に置きます。
defer_medium()
高い優先度で実行された場合、 defer_medium() は処理するタスクを低い優先度のキューの最後に置きます。高い優先度で実行されたのでなければ、defer_medium() は処理するタスクを直ちに呼び出します。
schedule() 関数は、 defer()が qelem に対して行なう処理と同様なことを clock に対して行ないます。schedule は指定されたタスク関数のための clock を作り、このタスクを特定のタイミングで実行するために clock_fdelay() を呼び出します。 defer()と同様に、 schedule() も引数をコピーし、タスクが実行される際にこれを渡します。
schedule() と同様な機能を持つ schedule_defer() では、指定した遅延時間の後、タスク関数を低い優先度のキューで実行します。