Jitter オブジェクトモデルの基本

Jitter オブジェクトは、従来から Max エクスターナルオブジェクトの開発に用いられているものと幾分異なったデータモデルを使用します。Jitter オブジェクトと従来のMax エクスターナルオブジェクトの大きな違いの第1は、Jitter オブジェクトはパッチャーそれ自身についての概念を全く持たないという点です。これによって、フレキシブルなインスタンス化や、Max パッチャー内と同様な、C、Java、JavaScript からの使用が可能になります。これらの Jitter オブジェクトの使用は、Max の「ラッパー」オブジェクトによってパッチャーに公開されます。これについては、後の章で論じます。

この章では、これらのどの言語でも使うことができるような Jitter オブジェクトの定義の基礎に話を絞って説明します。Jitter の第1のポイントはマトリックス処理とリアルタイムグラフィックスです。これらのタスクはオブジェクトモデルとは無関係であり、後の章の、マトリックスオペレータ (MOP) や OB3D オブジェクトの開発方法のところで説明します。

Max オブジェクトのように、Jitter オブジェクトは通常 C で書かれます。Jitter オブジェクトの開発に C++ を使うことも可能ですが、Jitterオブジェクト の開発では、オブジェクト指向言語の機能をあなたのオブジェクトの定義に使うことはできません。C++ や Java オブジェクトと同様、Jitter オブジェクトはメソッドとメンバ変数を持つクラスとして定義されます。私たちはこのメンバ変数を「アトリビュート」として参照します。C++ や Java と異なり、クラス定義、クラス継承、また、クラスインスタンスの使用を管理する言語仕様はありません。Jitter では、これらはすべてC 関数呼び出しのセットによって管理しなければなりません。この関数呼び出しはあなたのクラス、メソッドの使用、オブジェクトのアトリビュートの get および set を定義するものです。

Max および Jitter によるこれらのオブジェクトモデルの実装は、標準 C 関数のレジストリと、名前によって関連付けられたメソッドとアトリビュートに対してマッピングされる構造体メンバをメンテナンスすることによって実現されています。 他のコードがこれらのメソッドやアトリビュートを利用したい場合、Jitter オブジェクトに対して、保持しているレジストリから名前によってメソッドまたはアトリビュートを探し出すよう依頼します。これは、ダイナミックバインディング(動的バインディング)と呼ばれ、Smalltalk や Objective C のオブジェクトモデルと同じものです。C++ および Java は静的バインディングを使用しますが、静的バインディングでは、メソッドとメンバ変数を実行時に動的に探索するのではなく、コンパイル時に解決します。

Jitter クラスの定義

Jitter クラスは通常、your_object_name_init という形の名前による C の関数で定義されます。クラス定義は jit_class_new の呼び出しによって開始されます。これは指定された名前、コンストラクタ、デストラクタ、および C 構造体の中に保存されるオブジェクトのバイト単位でのサイズによって新しいクラスを作ります。これに、 jit_class_addmethod jit_class_addattr の呼び出しが続きます。これらはメソッドとアトリビュートを、対応する名前でクラス内に登録します。最後に、このクラスを jit_class_register の呼び出しによって登録します。最も小さいクラス定義の例は次のようなものになります。

typedef struct _jit_foo { t_jit_object ob; float myval; } t_jit_foo; static t_jit_class *_jit_foo_class=NULL; t_jit_err jit_foo_init(void) { long attrflags=0; t_jit_object *attr; //"jit_foo"という名前の新しいクラスを、コンストラクタ+デストラクタによって作ります _jit_foo_class = jit_class_new("jit_foo",(method)jit_foo_new, (method)jit_foo_free, sizeof(t_jit_foo), 0L); // クラスにメソッドを追加します jit_class_addmethod(jit_foo_scream, ”scream”, A_DEFLONG, 0L); // アトリビュートの定義 attr = jit_object_new( // jit_attr_offsetクラスのオブジェクトの _jit_sym_jit_attr_offset, // インスタンスを、"myval"という名前で作成 "myval", // _jit_sym_float32, // float32型 attrflags, // デフォルトのフラグ (method)0L, // デフォルトのゲッターアクセサ (method)0L, // デフォルトのセッターアクセサ calcoffset(t_jit_foo,myval)); // 構造体メンバのバイトオフセット // アトリビュートオブジェクトをクラスに追加 jit_class_addattr(_jit_foo_class, attr); // クラスの登録 jit_class_register(_jit_foo_class); return JIT_ERR_NONE; } // コンストラクタ t_jit_foo *jit_foo_new(void) { t_jit_foo *x; // オブジェクトのメモリ割当て if (x=jit_object_alloc(_jit_foo_class)) { // 成功した場合、必要な初期化を実行します x->myval = 0; } return x; } // デストラクタ void jit_foo_free(t_jit_foo *x) { // 必要なリソースをここで解放します } // scream メソッド void jit_foo_scream(t_jit_foo *x, long i) { post(“MY VALUE IS %f! AND MY ARGUMENT IS %d”, x->myval, i); }

上の例は、コンストラクタ jit_foo_new、デストラクタ jit_foo_free、1つの32ビット浮動小数点数によるアトリビュート myval、デフォルトのアクセサメソッドによってアクセスされるオブジェクト構造体のメンバ、そして、Max ウィンドウに現在の myval の値を表示するメソッド jit_foo_scream を持っています。

オブジェクト構造体

オブジェクトの各々のインスタンスは組織化されたメモリの一定の領域を占有します。この、メモリの組織を定義する C 構造体は、通常、「オブジェクト構造体」と呼ばれます。オブジェクト構造体が常に t_jit_object 型のエントリによって始まることは重要です。t_jit_objectの中には、クラスに関する特別な情報が保存されています。

C 構造体には、アトリビュートとして公開されているか否かを問わず、 情報を追加することができますが、オブジェクト構造体のサイズが 16384 バイトを超えることができないという点は重要です。このため、構造体のエントリとして大きい配列を定義することは、オブジェクト構造体のサイズがこの制限を超えてしまうかもしれないため、安全ではありません。追加のメモリが必要な場合、オブジェクト構造体は、コンストラクタの中で割当てられデストラクタの中で解放されるメモリへのポインタを持たなければなりません。

上のコードのクラスの登録では、オブジェクト構造体を使って、個々のオブジェクトインスタンスがどのくらいの大きさを持つか(すなわち、sizeof(t_jit_foo))、また、オブジェクト構造体の中で、アトリビュートの位置のオフセットが何バイトになるか(すなわち、calcoffset(t_jit_foo, myval))を、クラスの中に記録しています。オブジェクトのメソッドが呼び出されたとき、オブジェクト構造体のインスタンスが、オブジェクトメソッドを定義する C 関数の最初の引数として渡されます。このインスタンスは、C++ や Java で用いられる “this” キーワードと同様なものと考えられるでしょう。実際、C++ および Java の下での実装では、ここで純粋な C によって実装されているものと全く同じように動作します。

オブジェクト構造体のエントリは、オブジェクトメンバ変数と同様なものと考えられますが、メソッドは、C++ や Java で行われるような、単なるインスタンスの間接参照ではなく、関数によって呼び出されます。オブジェクトのメソッドのリスト、およびその他のクラス情報はあなたのオブジェクトのt_jit_object エントリによって参照されます。

コンストラクタ/デストラクタ

全てのオブジェクトで必須であり、最も重要な2つのメソッドは、コンストラクタとデストラクタです。これらは、通常、それぞれ、your_object_name_new your_object_name_free という名前を与えられます。

コンストラクタの役割は、オブジェクト構造体とオブジェクトが必要とする追加のリソースにメモリを割当て、初期化することです。オブジェクト構造体は jit_object_alloc を通してメモリの割当てを行われますが、これは同時に、、t_jit_object構造体のエントリを、関連したクラス情報を指すように初期化します。

クラス情報はあなたのグローバルクラス変数(例えば、_jit_foo_class)に属していてますが、これを jit_object_alloc の引数として渡します。この割当てを行なわずに、しかも、”myval” といった他の構造体のエントリが初期化される場合、メモリ割当てが成功したときには、あなたがそれを明確に初期化しなければなりません。

コンストラクタがオブジェクトのインスタンスにメモり割当てを行うため、他のオブジェクトメソッドと違って、コンストラクタを定義する関数の最初の引数としてオブジェクトのインスタンスを渡すことができない点に注意して下さい。

コンストラクタはまた、Wrinting Max Externals ドキュメントで定義されているものと同じ型の、型指定されたアーギュメントの記号を利用するオプションを持っています。すなわち、A_LONG, A_FLOAT, A_SYM, A_GIMME などです。通常、Jitter オブジェクトのコンストラクタはアーギュメントを持たないか、またはA_GIMME型のアーギュメント記号を持ちます。

Jitter の初期のバージョンでは、コンストラクタはしばしば、A_CANT という型記号を用いて、プライベートで、「型を持たない」ように指定されていました。この「型を持たないコンストラクタ」という旧式なスタイルはパッチャーや C に対して Jitter クラスを提示するためには動作しますが、現在では推奨されません。それは、Javascript や Java に対してクラスを提示するためには有効な型記号が必要で、これがないと、おそらくこの記号が空のリストになってしまうからです。

デストラクタの役割は、オブジェクト構造体自身以外の、メモり割当てが行われた全てのリソースを解放することです。オブジェクト構造体は、デストラクタが終了した後に解放されます。

メソッド

jit_class_addmethod 関数を使って、追加されるメソッドを定義することができます。次の例では、jit_foo_scream に結びつけられる scream メソッドを定義しています。ここでは、標準の第1引数であるオブジェクト構造体へのポインタを除き、追加される引数はありません。

これらのメソッドは、一般的な Max オブジェクトのメソッドのように、Wrinting Max Externals ドキュメントで定義されているものと同じ、型指定されたアーギュメントのための型記号(すなわち、A_LONGA_FLOATA_SYMA_GIMME)を持つことができます。

通常、Jitter オブジェクトでは、パブリックメソッドはアーギュメントを持たない、またはA_GIMME を使用する、あるいは低い優先度のバージョンであるA_DEFER_LOWA_USURP_LOW を使用するよう指定されます。これについては、後の章で説明します。

プライベートメソッドは、Max で型なしで定義する場合と同様に、A_CANTという型記号を用いて定義しなければなりません。オブジェクトメソッドは、C 関数から直接でも、あるいは jit_object_method jit_object_method_typed を使ってでも、C から呼び出すことができます。例えば、上記のjit_fooの例に関連づけられる次の呼出しの例は、同じ結果になります。

// scream メソッドを直接呼び出します jit_foo_scream(x, 74); // 動的な解決を行い、scream メソッドを呼び出します jit_object_method(x, gensym(“scream”), 74); // 動的な解決を行い、型を持つ atom のアーギュメントによって、scream // メソッドを呼び出します。 t_atom a[1]; jit_atom_setlong(a, 74); jit_object_method_typed(x, gensym(“scream”), 1, a, NULL);

jit_object_method および jit_object_method_typed 関数が行うことは、与えられたメソッドシンボルをオブジェクトのクラス情報から探し出し、与えられたシンボルに結びつけられた、対応する C 関数を呼び出すことです。

jit_object_method jit_object_method_typedの違いは、jit_object_methodはメソッドが型指定され、パブリックであることを必要とせず、メソッドシンボルに続く全てのアーギュメントを対応するメソッドに対してそのまま渡すという点です。そのため、あなたは、呼び出そうとするメソッドの型記号を知っていて、正しいアーギュメントを渡すことを義務づけられます。これはコンパイル時に型チェックが行われないため、あなたは、jit_object_method を通じて渡すアーギュメントに対して厳重に気を配らなくてはなりません。

メソッドを、型を指定された戻り値を持つよう、A_GIMMEBACK 型指定子を使って定義することもできます。このようなメソッドを呼び出したとき、jit_object_method_typedへの最後のアーギュメントは、呼び出された側によって値が書き込まれるt_atom へのポインタでなければなりません。これに関して、 および、型指定されたメソッドを要求する言語(例えばJava や JavaScript)とのバインドを行なうために他のプライベートメソッドを公開する「型指定のあるラッパー」に関しては後の章で述べます。

アトリビュート

jit_class_addattr を使ってクラスにアトリビュートを加えることができます。アトリビュートそれ自身は値をゲット(get) およびセット(set ) するための一般的なインタフェースを共有する Jitter オブジェクトです。アトリビュートインターフェイスに対応するどのようなクラスでも、与えられたクラスのアトリビュートを定義するために使えますが、現在使用されているいくつかの共通クラスが存在します。jit_attr_offsetは、オブジェクト構造体のいくつかのバイトオフセットにおいて、特定の型(char, long, float32, float64, symbol, atom)のスカラーアトリビュート (単一の値によるアトリビュート) を指定します。 jit_attr_offset_array は、オブジェクト構造体のいくつかのバイトオフセットにおいて、特定の型(char, long, float32, float64, symbol, atom)の配列(ベクタ)アトリビュートを指定します。そして、jit_attribute は、オブジェクトに基づいてインスタンス化することができる、より一般的なアトリビュートオブジェクトです。 現時点で私たちは、jit_attributeの使用法に関してはドキュメント化していません。クラス jit_attr_offset のためのコンストラクタは、次のようにプロトタイプ宣言されます。

t_jit_object *jit_attr_offset_new(char *name, t_symbol *type, long flags, method mget, method mset, long offset);

このコンストラクタに、クラス名を与え jit_object_new を通じて呼び出す場合、_jit_sym_jit_attr_offset gensym(“jit_attr_offset”) と等価なグローバル変数)を最初のパラメータとして渡し、その後に、コンストラクタに渡す上記のアーギュメントが続くようにしなければなりません。name アーギュメントはアトリビュート名を、ヌル文字で終わる C 文字列によって指定します。typeアーギュメントは、次のようなシンボルのうちの1つを使って、アトリビュートの型を指定します。

シンボル: _jit_sym_char_jit_sym_long_jit_sym_float32_jit_sym_float64_jit_sym_symbol_jit_sym_atom_jit_sym_object_jit_sym_pointer

最後の2つは、プライベートアトリビュートの場合にのみ役立ちます。これらの型は公開されていないか、または、Max メッセージの Atom の値からコンバートされるためです。flags アーギュメントはアトリビュートフラグを指定します。 これはビットごとの組み合わせによる次のような定数になります。

#define JIT_ATTR_GET_OPAQUE0x00000001 // 問合せ不可 #define JIT_ATTR_SET_OPAQUE0x00000002 // セット不可 #define JIT_ATTR_GET_OPAQUE_USER0x00000100 // ユーザーは問合せ不可 #define JIT_ATTR_SET_OPAQUE_USER0x00000200 // ユーザーはセット不可 #define JIT_ATTR_GET_DEFER0x00010000 // (deprecated) #define JIT_ATTR_GET_USURP0x00020000 // (deprecated) #define JIT_ATTR_GET_DEFER_LOW0x00040000 // 低い優先度での問合せ #define JIT_ATTR_GET_USURP_LOW0x00080000 // query in low, usurping #define JIT_ATTR_SET_DEFER0x01000000 // (deprecated) #define JIT_ATTR_SET_USURP0x02000000 // (deprecated) #define JIT_ATTR_SET_DEFER_LOW0x04000000 // 低い優先度でのセット #define JIT_ATTR_SET_USURP_LOW0x08000000 // set at low, usurping

通常、Jitter のアトリビュートは、フラグ JIT_ATTR_GET_DEFER_LOW JIT_ATTR_SET_USURP_LOW によって定義されます。このことは、パッチャーからの複数の問い合わせに対しては、問い合わせごとに応答が生じ、 高い優先度での値のセットが複数試みられた場合、最後に受け取った値による1つの呼出しにまとめられることを意味します。defer および usurp に関する詳しい情報は、Jitter Scheduling issues の章を参照して下さい。

mget アーギュメントはアトリビュートの値の問い合わせの際に使われる、アトリビュートの「ゲッター」アクセサメソッドを指定します。このアーギュメントが0(NULL)の場合には、デフォルトのゲッターアクセサが使われます。カスタムアクセサを定義する必要がある場合、カスタムアクセサは次のカスタムゲッターと互換性のあるプロトタイプおよび形式を持っていなければなりません。

t_jit_err jit_foo_myval_get(t_jit_foo *x, void *attr, long *ac, t_atom **av) { if ((*ac)&&(*av)) { //メモリが渡されます, これを使用します } else { // そうでない場合、メモリを割当てます *ac = 1; if (!(*av = jit_getbytes(sizeof(t_atom)*(*ac)))) { *ac = 0; return JIT_ERR_OUT_OF_MEM; } } jit_atom_setfloat(*av,x->myval); return JIT_ERR_NONE; }

ゲッターにメモリが渡されない場合、ゲッターは割当てられたメモリを必要とする点に注意して下さい。また、 attr アーギュメントはクラスのアトリビュートオブジェクトで、jit_object_method を使って、アトリビュートのフラグ、名前、フィルタ、などについての問い合わせを受けることができます。

mset アーギュメントは、アトリビュートの値をセットするために使われる、アトリビュートの「セッター」アクセサメソッドを指定します。このアーギュメントが0(NULL)の場合、デフォルトのセッターアクセサが使われます。カスタムアクセサを定義する必要がある場合、カスタムアクセサは次のカスタムセッターと互換性のあるプロトタイプおよび形式を持っていなければなりません。

t_jit_err jit_foo_myval_set(t_jit_foo *x, void *attr, long ac, t_atom *av) { if (ac&&av) { x->myval = jit_atom_getfloat(av); } else { // アーギュメントがない場合、0にセットされます x->myval = 0; } return JIT_ERR_NONE; }

offset アーギュメントは、オブジェクト構造体の中での、アトリビュートのバイトオフセットを指定します。これは、デフォルトのゲッターおよびセッターによって、アトリビュートの値を自動的に問い合わせたり、セットしたりするために使われます。両方ともカスタムアクセサが存在する場合、この値は無視されます。オブジェクト構造体の中にあるどのエントリにも対応しないオブジェクトアトリビュートを持ちたいと思う場合には、これを利用することが有効な戦略となります。

例えば、これは私たちが、jit.qt.movietime アトリビュートを実装している方法ですが、この場合、ムービータイムに関する情報を実際に保持していないオブジェクト構造体それ自身を操作するのではなく、現在時点のムービータイムを問い合わせ、設定するための QuickTime API 呼出しを行うカスタムゲッターとカスタムセッターを使っています。このような例では、このオフセットを0にセットしなければなりません。アトリビュートを作った後、アトリビュートは jit_class_addattr 関数を使って Jitter クラスに加えなければなりません。

t_jit_err jit_class_addattr(void *c, t_jit_object *attr);

まとめて次に示しますが、上で定義したカスタムゲッターおよびセッター関数によって jit_attribute_offset を定義するためには、次のような呼出しを行う必要があるでしょう。

long attrflags = JIT_ATTR_GET_DEFER_LOW | JIT_ATTR_SET_USURP_LOW; t_jit_object *attr = jit_object_new(_jit_sym_jit_attr_offset, "myval", _jit_sym_float32, attrflags, (method)jit_foo_myval_get, (method)jit_foo_myval_set, NULL); jit_class_addattr(_jit_foo_class, attr);

また、デフォルトのゲッターとセッターメソッドを使って、極めて標準的なjit_attribute_offsetを定義する場合は、次のようになります。

long attrflags = JIT_ATTR_GET_DEFER_LOW | JIT_ATTR_SET_USURP_LOW; t_jit_object *attr = jit_object_new(_jit_sym_jit_attr_offset, "myval", _jit_sym_float32, attrflags, (method)NULL, (method)NULL, calcoffset(t_jit_foo, myval)); jit_class_addattr(_jit_foo_class, attr);

アトリビュートの配列

アトリビュートは、1つの値の参照だけでなく、データの配列を参照することもできます。クラス jit_attribute_offset_array はこのインスタンスとして用いられます。このクラスのコンストラクタ jit_attr_offset_array は次のようなプロトタイプを持っています。

t_jit_object *jit_attr_offset_array_new(char *name, t_symbol *type, long size,long flags, method mget, method mset, long offsetcount, long offset);

コンストラクタをjit_object_new を通じて呼び出す場合、クラス名 _jit_sym_jit_attr_offset_array gensym(“jit_attr_offset_array”) と同等のグローバル変数)を最初のパラメータとして渡し、コンストラクタに渡す上記のアーギュメントをそれに続けなければなりません。nametypeflagsmgetmsetoffset はすでに述べられたものと同じです。

size アーギュメントは配列の最大の長さ(maximum - Jitter オブジェクト構造体の中の配列のメモリ割当てサイズ)を指定します。offsetcount はオブジェクト構造体のバイトオフセットを指定します。これは、問い合わせやセットを行うことができる配列の実際の長さになります。この値は long として指定され、デフォルトのゲッター、およびセッターがアトリビュートの値の問い合わせやセットを行う場合に用いられます。 

jit_attr_offset オブジェクトの場合と同様、両方のカスタムアクセサを持っている場合にはこの値は無視されます。次のサンプルリストは、オブジェクト定義のために、jit_attr_offset_array クラスのシンプルなインスタンスを作る例を示しています。

typedef struct _jit_foo { t_jit_objectob; longmyarray[10]; // この配列は最大10個のエントリを持ちます longmyarraycount; // 実際に使用される数 } t_jit_foo; long attrflags = JIT_ATTR_GET_DEFER_LOW | JIT_ATTR_SET_USURP_LOW; t_jit_object *attr = jit_object_new(_jit_sym_jit_attr_offset_array, _jit_sym_long,10, attrflags, (method)0L, (method)0L, calcoffset(t_jit_foo, myarraycount), calcoffset(t_jit_foo, myarray)); jit_class_addattr(_jit_foo_class, attr);

アトリビュートの通知

オブジェクトの登録と通知に関するテーマは次の章でより詳しく説明しますが、登録や通知は全ての型のアトリビュート(例えば、jit_attr_offsetjit_attr_offset_arrayjit_attribute)を対象としています。登録が行われると、アトリビュートの値がセットされるごとに、接続されている全てのクライアントオブジェクトに対して自動的に通知が行われます。